Home | History | Annotate | Download | only in firmware_ChipFwUpdate
      1 # Copyright 2017 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 """This is a FAFT test for TCPC firmware updates.
      6 
      7 This test forces TCPC firmware updates for the specified TCPCs.
      8 
      9 The test is invoked with additional arguments to specify alternate
     10 TCPC firmware blobs.  These are "edited" into the DUT's bios.bin
     11 normally extracted from the system shellball.  Then, the bios.bin is
     12 flashed into the DUT and the DUT is rebooted.
     13 
     14 Under normal conditions, the TCPC firmware blobs will be updated as
     15 part of software sync when the DUT reboots.  Software sync checks that
     16 the new firmware is actually running on the TCPCs, however it can also
     17 be audited after the fact using the firmware_CompareChipFwToShellBall
     18 FAFT test for independent verification.
     19 
     20 This test should be invoked twice: the 1st time to "downgrade" the
     21 TCPC firmware, then a 2nd time to restore the production TCPC
     22 firmware.  Alternatively, the system can be reflashed with a
     23 production bios.bin (and rebooted) to restore the TCPC firmware.
     24 
     25 The parade ps8751 (and similar) parts can be re-flashed indefinitely.
     26 However, the analogix parts can only be updated about 100 times which
     27 means it is not feasible to include them in continuous automated
     28 testing.
     29 
     30 This test will only replace existing TCPC firmware blobs in bios.bin.
     31 If the corresponding binary blobs are not found in cbfs, it is assumed
     32 that the release does not support the requested TCPCs.  Alternatively,
     33 a bios.bin can be specified when invoking the test that will be used
     34 insteade of the bios.bin normally extracted from the DUT's system
     35 shellball.
     36 """
     37 
     38 import logging
     39 import os
     40 import tempfile
     41 
     42 from autotest_lib.client.common_lib import error
     43 from autotest_lib.client.common_lib import utils
     44 from autotest_lib.client.common_lib.cros import chip_utils
     45 from autotest_lib.server.cros import vboot_constants as vboot
     46 from autotest_lib.server.cros.faft.firmware_test import FirmwareTest
     47 
     48 
     49 class firmware_ChipFwUpdate(FirmwareTest):
     50 
     51     """Updates DUT firmware image with specified firmware blobs.
     52 
     53     If a new bios.bin is offered, it replaces the
     54     existing bios.bin.
     55     Then, if new chip firmware blobs are offered, they
     56     replace existing firmware blobs in bios.bin.
     57     Finally the system shellball is repacked.
     58 
     59     A reboot must be issued for the new firmware to be applied
     60     during software sync.
     61 
     62     Use the firmware_ChipFwUpdate test to verify that the new
     63     firmware was applied.
     64     """
     65     version = 1
     66 
     67     BIOS = 'bios.bin'
     68     HEXDUMP = 'hexdump -v -e \'1/1 "0x%02x\\n"\''
     69 
     70     def initialize(self, host, cmdline_args):
     71         dict_args = utils.args_to_dict(cmdline_args)
     72         super(firmware_ChipFwUpdate,
     73               self).initialize(host, cmdline_args)
     74 
     75         self.new_bios_path = dict_args['bios'] if 'bios' in dict_args else None
     76 
     77         self.clear_set_gbb_flags(
     78             vboot.GBB_FLAG_DISABLE_EC_SOFTWARE_SYNC |
     79             vboot.GBB_FLAG_DISABLE_PD_SOFTWARE_SYNC, 0)
     80 
     81         self.dut_bios_path = None
     82         self.cbfs_work_dir = None
     83 
     84         # set of chip types found in CBFS
     85         self.cbfs_chip_types = set()
     86         # dict of chip FW updates from the cmd line
     87         self.req_chip_updates = {}
     88 
     89         # see if comand line specified new firmware blobs
     90         # for chips we know about
     91 
     92         for chip in chip_utils.chip_id_map.itervalues():
     93             chip_name = chip.chip_name
     94             if chip_name not in dict_args:
     95                 continue
     96             chip_file = dict_args[chip_name]
     97             if not os.path.exists(chip_file):
     98                 raise error.TestError('file %s not found' % chip_file)
     99             c = chip()
    100             c.set_from_file(chip_file)
    101             if chip_name in self.req_chip_updates:
    102                 raise error.TestError('multiple %s args' % chip_name)
    103             logging.info('request chip %s fw 0x%02x from command line',
    104                          c.chip_name, c.fw_ver)
    105             self.req_chip_updates[chip_name] = c
    106 
    107     def dut_setup_cbfs(self):
    108         """Sets up a work dir for cbfstool.
    109 
    110         Creates a fresh temp. dir for cbfstool to manipulate bios.bin.
    111         """
    112 
    113         cbfs_path = self.faft_client.updater.cbfs_setup_work_dir()
    114         bios_relative_path = self.faft_client.updater.get_bios_relative_path()
    115         self.cbfs_work_dir = cbfs_path
    116         self.dut_bios_path = os.path.join(cbfs_path, bios_relative_path)
    117 
    118     def cbfs_extract_chips(self):
    119         """Extracts interesting firmware blobs from cbfs.
    120 
    121         Iterates over requested chip updates and looks for corresponding
    122         firmware blobs in cbfs.  Firmware blobs are then extracted into
    123         cbfs_work_dir.
    124         """
    125 
    126         for chip in self.req_chip_updates.itervalues():
    127             logging.info('checking for %s firmware in %s',
    128                          chip.chip_name, self.BIOS)
    129 
    130             if not self.faft_client.updater.cbfs_extract_chip(chip.fw_name):
    131                 logging.warning('%s firmware not bundled in %s',
    132                                 chip.chip_name, self.BIOS)
    133                 continue
    134 
    135             hashblob = self.faft_client.updater.cbfs_get_chip_hash(
    136                 chip.fw_name)
    137             if not hashblob:
    138                 logging.warning('%s firmware hash not extracted from %s',
    139                                 chip.chip_name, self.BIOS)
    140                 continue
    141 
    142             bundled_fw_ver = chip.fw_ver_from_hash(hashblob)
    143             if not bundled_fw_ver:
    144                 raise error.TestFail(
    145                     'could not decode %s firmware hash: %s' % (
    146                         chip.chip_name, hashblob))
    147 
    148             self.cbfs_chip_types.add(type(chip))
    149             logging.info('%s bundled firmware for %s is version %s',
    150                          self.BIOS, chip.chip_name, bundled_fw_ver)
    151 
    152     def cbfs_replace_chips(self, host):
    153         """Iterates over known chips in cbfs.
    154 
    155         For each chip that has an update specified on the command line,
    156         copies the firmware (bin, hash) to DUT and updates cbfs in
    157         bios.bin.
    158 
    159         Args:
    160             host: host handle to the DUT.
    161         """
    162 
    163         for chip in self.cbfs_chip_types:
    164             chip_name = chip.chip_name
    165             logging.info('replacing %s firmware in %s', chip_name, self.BIOS)
    166 
    167             fw_update = self.req_chip_updates[chip_name]
    168             fw_hash = fw_update.compute_hash_bytes()
    169             (fd, n) = tempfile.mkstemp()
    170             with os.fdopen(fd, 'wb') as f:
    171                 f.write(fw_hash)
    172 
    173             try:
    174                 host.send_file(n,
    175                                os.path.join(
    176                                    self.cbfs_work_dir,
    177                                    fw_update.cbfs_hash_name))
    178             finally:
    179                 os.unlink(n)
    180 
    181             host.send_file(fw_update.fw_file_name,
    182                            os.path.join(
    183                                self.cbfs_work_dir,
    184                                fw_update.cbfs_bin_name))
    185 
    186             if not self.faft_client.updater.cbfs_replace_chip(
    187                     fw_update.fw_name):
    188                 raise error.TestFail('could not replace %s blobs in cbfs' %
    189                                      fw_update.chip_name)
    190 
    191     def dut_sign_and_flash_bios(self, host):
    192         """Signs the BIOS and flashes the DUT with it.
    193 
    194         Args:
    195             host: host handle to the DUT.
    196         """
    197 
    198         if not self.faft_client.updater.cbfs_sign_and_flash():
    199             raise error.TestFail('could not re-sign %s' % self.dut_bios_path)
    200         host.reboot()
    201 
    202     def run_once(self, host):
    203         # Make sure the client library is on the device so that the proxy
    204         # code is there when we try to call it.
    205 
    206         if not self.req_chip_updates:
    207             logging.info('no FW updates requested, skipping test')
    208             return
    209 
    210         self.dut_setup_cbfs()
    211         if self.new_bios_path:
    212             host.send_file(self.new_bios_path, self.dut_bios_path)
    213 
    214         self.cbfs_extract_chips()
    215         if not self.cbfs_chip_types:
    216             logging.info('firmware does not support requested updates, '
    217                          'skipping test')
    218             return
    219 
    220         self.cbfs_replace_chips(host)
    221         self.dut_sign_and_flash_bios(host)
    222