• Home
  • Line#
  • Scopes#
  • Navigate#
  • Raw
  • Download
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
7This test forces TCPC firmware updates for the specified TCPCs.
8
9The test is invoked with additional arguments to specify alternate
10TCPC firmware blobs.  These are "edited" into the DUT's bios.bin
11normally extracted from the system shellball.  Then, the bios.bin is
12flashed into the DUT and the DUT is rebooted.
13
14Under normal conditions, the TCPC firmware blobs will be updated as
15part of software sync when the DUT reboots.  Software sync checks that
16the new firmware is actually running on the TCPCs, however it can also
17be audited after the fact using the firmware_CompareChipFwToShellBall
18FAFT test for independent verification.
19
20This test should be invoked twice: the 1st time to "downgrade" the
21TCPC firmware, then a 2nd time to restore the production TCPC
22firmware.  Alternatively, the system can be reflashed with a
23production bios.bin (and rebooted) to restore the TCPC firmware.
24
25The parade ps8751 (and similar) parts can be re-flashed indefinitely.
26However, the analogix parts can only be updated about 100 times which
27means it is not feasible to include them in continuous automated
28testing.
29
30This test will only replace existing TCPC firmware blobs in bios.bin.
31If the corresponding binary blobs are not found in cbfs, it is assumed
32that the release does not support the requested TCPCs.  Alternatively,
33a bios.bin can be specified when invoking the test that will be used
34insteade of the bios.bin normally extracted from the DUT's system
35shellball.
36"""
37
38import logging
39import os
40import tempfile
41
42from autotest_lib.client.common_lib import error
43from autotest_lib.client.common_lib import utils
44from autotest_lib.client.common_lib.cros import chip_utils
45from autotest_lib.server.cros import vboot_constants as vboot
46from autotest_lib.server.cros.faft.firmware_test import FirmwareTest
47
48
49class 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