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_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_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._servo_version == 'servo_v4_with_ccd_cr50':
    274             self._program_cmd += ' --raiden'
    275 
    276 
    277 class ProgrammerV2(object):
    278     """Main programmer class which provides programmer for BIOS and EC with
    279     servo V2."""
    280 
    281     def __init__(self, servo):
    282         self._servo = servo
    283         self._valid_boards = self._get_valid_v2_boards()
    284         self._bios_programmer = self._factory_bios(self._servo)
    285         self._ec_programmer = self._factory_ec(self._servo)
    286 
    287 
    288     @staticmethod
    289     def _get_valid_v2_boards():
    290         """Greps servod config files to look for valid v2 boards.
    291 
    292         @return A list of valid board names.
    293         """
    294         site_packages_paths = site.getsitepackages()
    295         SERVOD_CONFIG_DATA_DIR = None
    296         for p in site_packages_paths:
    297             servo_data_path = os.path.join(p, 'servo', 'data')
    298             if os.path.exists(servo_data_path):
    299                 SERVOD_CONFIG_DATA_DIR = servo_data_path
    300                 break
    301         if not SERVOD_CONFIG_DATA_DIR:
    302             raise ProgrammerError(
    303                     'Unable to locate data directory of Python servo module')
    304         SERVOFLEX_V2_R0_P50_CONFIG = 'servoflex_v2_r0_p50.xml'
    305         SERVO_CONFIG_GLOB = 'servo_*_overlay.xml'
    306         SERVO_CONFIG_REGEXP = 'servo_(?P<board>.+)_overlay.xml'
    307 
    308         def is_v2_compatible_board(board_config_path):
    309             """Check if the given board config file is v2-compatible.
    310 
    311             @param board_config_path: Path to a board config XML file.
    312 
    313             @return True if the board is v2-compatible; False otherwise.
    314             """
    315             configs = []
    316             def get_all_includes(config_path):
    317                 """Get all included XML config names in the given config file.
    318 
    319                 @param config_path: Path to a servo config file.
    320                 """
    321                 root = xml.etree.ElementTree.parse(config_path).getroot()
    322                 for element in root.findall('include'):
    323                     include_name = element.find('name').text
    324                     configs.append(include_name)
    325                     get_all_includes(os.path.join(
    326                             SERVOD_CONFIG_DATA_DIR, include_name))
    327 
    328             get_all_includes(board_config_path)
    329             return True if SERVOFLEX_V2_R0_P50_CONFIG in configs else False
    330 
    331         result = []
    332         board_overlays = glob.glob(
    333                 os.path.join(SERVOD_CONFIG_DATA_DIR, SERVO_CONFIG_GLOB))
    334         for overlay_path in board_overlays:
    335             if is_v2_compatible_board(overlay_path):
    336                 result.append(re.search(SERVO_CONFIG_REGEXP,
    337                                         overlay_path).group('board'))
    338         return result
    339 
    340 
    341     def _get_flashrom_programmer(self, servo):
    342         """Gets a proper flashrom programmer.
    343 
    344         @param servo: A servo object.
    345 
    346         @return A programmer for flashrom.
    347         """
    348         return FlashromProgrammer(servo)
    349 
    350 
    351     def _factory_bios(self, servo):
    352         """Instantiates and returns (bios, ec) programmers 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         _bios_prog = None
    360         _board = servo.get_board()
    361 
    362         logging.debug('Setting up BIOS programmer for board: %s', _board)
    363         if _board in self._valid_boards:
    364             _bios_prog = self._get_flashrom_programmer(servo)
    365         else:
    366             logging.warning('No BIOS programmer found for board: %s', _board)
    367 
    368         return _bios_prog
    369 
    370 
    371     def _factory_ec(self, servo):
    372         """Instantiates and returns ec programmer for the board.
    373 
    374         @param servo: A servo object.
    375 
    376         @return A programmer for ec. If the programmer is not supported
    377             for the board, None will be returned.
    378         """
    379         _ec_prog = None
    380         _board = servo.get_board()
    381 
    382         logging.debug('Setting up EC programmer for board: %s', _board)
    383         if _board in self._valid_boards:
    384             _ec_prog = FlashECProgrammer(servo)
    385         else:
    386             logging.warning('No EC programmer found for board: %s', _board)
    387 
    388         return _ec_prog
    389 
    390 
    391     def program_bios(self, image):
    392         """Programs the DUT with provide bios image.
    393 
    394         @param image: (required) location of bios image file.
    395 
    396         """
    397         self._bios_programmer.prepare_programmer(image)
    398         self._bios_programmer.program()
    399 
    400 
    401     def program_ec(self, image):
    402         """Programs the DUT with provide ec image.
    403 
    404         @param image: (required) location of ec image file.
    405 
    406         """
    407         self._ec_programmer.prepare_programmer(image)
    408         self._ec_programmer.program()
    409 
    410 
    411 class ProgrammerV2RwOnly(ProgrammerV2):
    412     """Main programmer class which provides programmer for only updating the RW
    413     portion of BIOS with servo V2.
    414 
    415     It does nothing on EC, as EC software sync on the next boot will
    416     automatically overwrite the EC RW portion, using the EC RW image inside
    417     the BIOS RW image.
    418 
    419     """
    420 
    421     def _get_flashrom_programmer(self, servo):
    422         """Gets a proper flashrom programmer.
    423 
    424         @param servo: A servo object.
    425 
    426         @return A programmer for flashrom.
    427         """
    428         return FlashromProgrammer(servo, keep_ro=True)
    429 
    430 
    431     def program_ec(self, image):
    432         """Programs the DUT with provide ec image.
    433 
    434         @param image: (required) location of ec image file.
    435 
    436         """
    437         # Do nothing. EC software sync will update the EC RW.
    438         pass
    439 
    440 
    441 class ProgrammerV3(object):
    442     """Main programmer class which provides programmer for BIOS and EC with
    443     servo V3.
    444 
    445     Different from programmer for servo v2, programmer for servo v3 does not
    446     try to validate if the board can use servo V3 to update firmware. As long as
    447     the servod process running in beagblebone with given board, the program will
    448     attempt to flash bios and ec.
    449 
    450     """
    451 
    452     def __init__(self, servo):
    453         self._servo = servo
    454         self._bios_programmer = FlashromProgrammer(servo)
    455         self._ec_programmer = FlashECProgrammer(servo)
    456 
    457 
    458     def program_bios(self, image):
    459         """Programs the DUT with provide bios image.
    460 
    461         @param image: (required) location of bios image file.
    462 
    463         """
    464         self._bios_programmer.prepare_programmer(image)
    465         self._bios_programmer.program()
    466 
    467 
    468     def program_ec(self, image):
    469         """Programs the DUT with provide ec image.
    470 
    471         @param image: (required) location of ec image file.
    472 
    473         """
    474         self._ec_programmer.prepare_programmer(image)
    475         self._ec_programmer.program()
    476 
    477 
    478 class ProgrammerV3RwOnly(ProgrammerV3):
    479     """Main programmer class which provides programmer for only updating the RW
    480     portion of BIOS with servo V3.
    481 
    482     It does nothing on EC, as EC software sync on the next boot will
    483     automatically overwrite the EC RW portion, using the EC RW image inside
    484     the BIOS RW image.
    485 
    486     """
    487 
    488     def __init__(self, servo):
    489         self._servo = servo
    490         self._bios_programmer = FlashromProgrammer(servo, keep_ro=True)
    491 
    492 
    493     def program_ec(self, image):
    494         """Programs the DUT with provide ec image.
    495 
    496         @param image: (required) location of ec image file.
    497 
    498         """
    499         # Do nothing. EC software sync will update the EC RW.
    500         pass
    501 
    502 
    503 class ProgrammerDfu(object):
    504     """Main programmer class which provides programmer for Base EC via DFU.
    505 
    506     It programs through the DUT, i.e. running the flash_ec script on DUT
    507     instead of running it in beaglebone (a host of the servo board).
    508     It is independent of the version of servo board as long as the servo
    509     board has the ec_boot_mode interface.
    510 
    511     """
    512 
    513     def __init__(self, servo, cros_host):
    514         self._servo = servo
    515         self._cros_host = cros_host
    516         # Get the chip name of the base EC and append '_dfu' to it, like
    517         # 'stm32' -> 'stm32_dfu'.
    518         ec_chip = servo.get(servo.get_base_board() + '_ec_chip') + '_dfu'
    519         self._ec_programmer = FlashECProgrammer(servo, cros_host, ec_chip)
    520 
    521 
    522     def program_ec(self, image):
    523         """Programs the DUT with provide ec image.
    524 
    525         @param image: (required) location of ec image file.
    526 
    527         """
    528         self._ec_programmer.prepare_programmer(image)
    529         ec_boot_mode = self._servo.get_base_board() + '_ec_boot_mode'
    530         try:
    531             self._servo.set(ec_boot_mode, 'on')
    532             # Power cycle the base to enter DFU mode
    533             self._cros_host.run('ectool gpioset PP3300_DX_BASE 0')
    534             self._cros_host.run('ectool gpioset PP3300_DX_BASE 1')
    535             self._ec_programmer.program()
    536         finally:
    537             self._servo.set(ec_boot_mode, 'off')
    538             # Power cycle the base to back normal mode
    539             self._cros_host.run('ectool gpioset PP3300_DX_BASE 0')
    540             self._cros_host.run('ectool gpioset PP3300_DX_BASE 1')
    541