xref: /aosp_15_r20/external/autotest/server/site_tests/firmware_ChipFwUpdate/firmware_ChipFwUpdate.py (revision 9c5db1993ded3edbeafc8092d69fe5de2ee02df7)
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"""
37import binascii
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    def initialize(self, host, cmdline_args):
68        dict_args = utils.args_to_dict(cmdline_args)
69        super(firmware_ChipFwUpdate,
70              self).initialize(host, cmdline_args)
71
72        self.new_bios_path = dict_args['bios'] if 'bios' in dict_args else None
73
74        self.clear_set_gbb_flags(
75            vboot.GBB_FLAG_DISABLE_EC_SOFTWARE_SYNC |
76            vboot.GBB_FLAG_DISABLE_AUXFW_SOFTWARE_SYNC, 0)
77
78        self.dut_bios_path = None
79        self.cbfs_work_dir = None
80
81        # set of chip types found in CBFS
82        self.cbfs_chip_types = set()
83        # dict of chip FW updates from the cmd line
84        self.req_chip_updates = {}
85
86        # see if comand line specified new firmware blobs
87        # for chips we know about
88
89        for chip_type in chip_utils.chip_id_map.values():
90            chip_name = chip_type.chip_name
91            if chip_name not in dict_args:
92                continue
93            chip_file = dict_args[chip_name]
94            if not os.path.exists(chip_file):
95                raise error.TestError('file %s not found' % chip_file)
96
97            chip = chip_type()
98            chip.set_from_file(chip_file)
99
100            if chip_name in self.req_chip_updates:
101                raise error.TestError('multiple %s args' % chip_name)
102
103            fw_ver_desc = ''
104            if chip.fw_ver:
105                fw_ver_desc = ' (version 0x%02x)' % chip.fw_ver
106            logging.info('got %s firmware from args: %s%s', chip.chip_name,
107                         chip_file, fw_ver_desc)
108            self.req_chip_updates[chip_name] = chip
109
110    def dut_setup_cbfs(self):
111        """Sets up a work dir for cbfstool.
112
113        Creates a fresh temp. dir for cbfstool to manipulate bios.bin.
114        """
115
116        cbfs_path = self.faft_client.updater.cbfs_setup_work_dir()
117        bios_relative_path = self.faft_client.updater.get_bios_relative_path()
118        self.cbfs_work_dir = cbfs_path
119        self.dut_bios_path = os.path.join(cbfs_path, bios_relative_path)
120
121    def cbfs_extract_chips(self):
122        """Extracts interesting firmware blobs from cbfs.
123
124        Iterates over requested chip updates and looks for corresponding
125        firmware blobs in cbfs.  Firmware blobs are then extracted into
126        cbfs_work_dir.
127        """
128
129        for chip in self.req_chip_updates.values():
130            logging.info('checking for %s firmware in CBFS', chip.chip_name)
131
132            if not self.faft_client.updater.cbfs_extract_chip(
133                    chip.fw_name, chip.extension, chip.hash_extension):
134                logging.warning('%s firmware not bundled in CBFS',
135                                chip.chip_name)
136                continue
137
138            hashblob = self.faft_client.updater.cbfs_get_chip_hash(
139                    chip.fw_name, chip.hash_extension)
140            if hashblob:
141                if hasattr(chip, 'fw_ver_from_hash'):
142                    bundled_fw_ver = chip.fw_ver_from_hash(hashblob)
143                    if bundled_fw_ver is None:
144                        raise error.TestFail(
145                                'could not determine version from %s firmware '
146                                'hash: %s' % (chip.chip_name, hashblob))
147                    logging.info('CBFS bundled firmware for %s is version %s',
148                                 chip.chip_name, bundled_fw_ver)
149                else:
150                    logging.info('CBFS bundled firmware for %s has hash %s',
151                                 chip.chip_name, hashblob)
152            else:
153                logging.warning('%s firmware hash not extracted from CBFS',
154                                chip.chip_name)
155            self.cbfs_chip_types.add(type(chip))
156
157    def cbfs_replace_chips(self, host):
158        """Iterates over known chips in cbfs.
159
160        For each chip that has an update specified on the command line,
161        copies the firmware (bin, hash) to DUT and updates cbfs in
162        bios.bin.
163
164        Args:
165            host: host handle to the DUT.
166        """
167
168        for chip_type in self.cbfs_chip_types:
169            chip_name = chip_type.chip_name
170            logging.info('replacing %s firmware in CBFS', chip_name)
171
172            fw_update = self.req_chip_updates[chip_name]
173            fw_hash = fw_update.compute_hash_bytes()
174            logging.info("New file's hash is: %s", binascii.hexlify(fw_hash))
175            (fd, n) = tempfile.mkstemp()
176            with os.fdopen(fd, 'wb') as f:
177                f.write(fw_hash)
178
179            try:
180                host.send_file(n,
181                               os.path.join(
182                                   self.cbfs_work_dir,
183                                   fw_update.cbfs_hash_name))
184            finally:
185                os.unlink(n)
186
187            host.send_file(fw_update.fw_file_name,
188                           os.path.join(
189                               self.cbfs_work_dir,
190                               fw_update.cbfs_bin_name))
191
192            if not self.faft_client.updater.cbfs_replace_chip(
193                    fw_update.fw_name, fw_update.extension,
194                    fw_update.hash_extension):
195                raise error.TestFail('could not replace %s blobs in cbfs' %
196                                     fw_update.chip_name)
197
198    def dut_sign_and_flash_bios(self, host):
199        """Signs the BIOS and flashes the DUT with it.
200
201        Args:
202            host: host handle to the DUT.
203        """
204
205        if not self.faft_client.updater.cbfs_sign_and_flash():
206            raise error.TestFail('could not re-sign %s' % self.dut_bios_path)
207        host.reboot()
208
209    def run_once(self, host):
210        """Runs a single iteration of the test."""
211        # Make sure the client library is on the device so that the proxy
212        # code is there when we try to call it.
213
214        if not self.req_chip_updates:
215            logging.info('no FW updates requested, skipping test')
216            return
217
218        self.dut_setup_cbfs()
219        if self.new_bios_path:
220            host.send_file(self.new_bios_path, self.dut_bios_path)
221
222        self.cbfs_extract_chips()
223        if not self.cbfs_chip_types:
224            logging.info('firmware does not support requested updates, '
225                         'skipping test')
226            return
227
228        self.cbfs_replace_chips(host)
229        self.dut_sign_and_flash_bios(host)
230