xref: /aosp_15_r20/external/autotest/server/cros/faft/cr50_test.py (revision 9c5db1993ded3edbeafc8092d69fe5de2ee02df7)
1*9c5db199SXin Li# Lint as: python2, python3
2*9c5db199SXin Li# Copyright 2017 The Chromium OS Authors. All rights reserved.
3*9c5db199SXin Li# Use of this source code is governed by a BSD-style license that can be
4*9c5db199SXin Li# found in the LICENSE file.
5*9c5db199SXin Li
6*9c5db199SXin Lifrom __future__ import print_function
7*9c5db199SXin Li
8*9c5db199SXin Liimport logging
9*9c5db199SXin Liimport os
10*9c5db199SXin Liimport pprint
11*9c5db199SXin Liimport six
12*9c5db199SXin Liimport time
13*9c5db199SXin Li
14*9c5db199SXin Lifrom autotest_lib.client.bin import utils
15*9c5db199SXin Lifrom autotest_lib.client.common_lib import error, utils
16*9c5db199SXin Lifrom autotest_lib.client.common_lib.cros import cr50_utils, tpm_utils
17*9c5db199SXin Lifrom autotest_lib.server.cros import filesystem_util, gsutil_wrapper
18*9c5db199SXin Lifrom autotest_lib.server.cros.faft.firmware_test import FirmwareTest
19*9c5db199SXin Li
20*9c5db199SXin Li
21*9c5db199SXin Liclass Cr50Test(FirmwareTest):
22*9c5db199SXin Li    """Base class that sets up helper objects/functions for cr50 tests."""
23*9c5db199SXin Li    version = 1
24*9c5db199SXin Li
25*9c5db199SXin Li    RELEASE_POOLS = ['faft-cr50-experimental', 'faft-cr50']
26*9c5db199SXin Li    RESPONSE_TIMEOUT = 180
27*9c5db199SXin Li    GS_PRIVATE = 'gs://chromeos-localmirror-private/distfiles/'
28*9c5db199SXin Li    # Prod signed test images are stored in the private cr50 directory.
29*9c5db199SXin Li    GS_PRIVATE_PROD = GS_PRIVATE + 'cr50/'
30*9c5db199SXin Li    # Node locked test images are in this private debug directory.
31*9c5db199SXin Li    GS_PRIVATE_DBG = GS_PRIVATE + 'chromeos-cr50-debug-0.0.11/'
32*9c5db199SXin Li    GS_PUBLIC = 'gs://chromeos-localmirror/distfiles/'
33*9c5db199SXin Li    CR50_PROD_FILE = 'cr50.r0.0.1*.w%s%s.tbz2'
34*9c5db199SXin Li    CR50_DEBUG_FILE = '*/cr50.dbg.%s.bin.*%s'
35*9c5db199SXin Li    CR50_ERASEFLASHINFO_FILE = (
36*9c5db199SXin Li            '*/cr50_Unknown_NodeLocked-%s_cr50-accessory-mp.bin')
37*9c5db199SXin Li    CR50_QUAL_VERSION_FILE = 'chromeos-cr50-QUAL_VERSION'
38*9c5db199SXin Li    NONE = 0
39*9c5db199SXin Li    # Saved the original device state during init.
40*9c5db199SXin Li    INITIAL_IMAGE_STATE = 1 << 0
41*9c5db199SXin Li    # Saved the original image, the device image, and the debug image. These
42*9c5db199SXin Li    # images are needed to be able to restore the original image and board id.
43*9c5db199SXin Li    DEVICE_IMAGES = 1 << 1
44*9c5db199SXin Li    DBG_IMAGE = 1 << 2
45*9c5db199SXin Li    ERASEFLASHINFO_IMAGE = 1 << 3
46*9c5db199SXin Li    # Different attributes of the device state require the test to download
47*9c5db199SXin Li    # different images. STATE_IMAGE_RESTORES is a dictionary of the state each
48*9c5db199SXin Li    # image type can restore.
49*9c5db199SXin Li    STATE_IMAGE_RESTORES = {
50*9c5db199SXin Li            DEVICE_IMAGES: ['prod_version', 'prepvt_version'],
51*9c5db199SXin Li            DBG_IMAGE: ['running_image_ver', 'running_image_bid', 'chip_bid'],
52*9c5db199SXin Li            ERASEFLASHINFO_IMAGE: ['chip_bid'],
53*9c5db199SXin Li    }
54*9c5db199SXin Li    PP_SHORT_INTERVAL = 3
55*9c5db199SXin Li
56*9c5db199SXin Li    def initialize(self,
57*9c5db199SXin Li                   host,
58*9c5db199SXin Li                   cmdline_args,
59*9c5db199SXin Li                   full_args,
60*9c5db199SXin Li                   restore_cr50_image=False,
61*9c5db199SXin Li                   restore_cr50_board_id=False,
62*9c5db199SXin Li                   provision_update=False):
63*9c5db199SXin Li        self._saved_state = self.NONE
64*9c5db199SXin Li        self._raise_error_on_mismatch = not restore_cr50_image
65*9c5db199SXin Li        self._provision_update = provision_update
66*9c5db199SXin Li        self.tot_test_run = full_args.get('tot_test_run', '').lower() == 'true'
67*9c5db199SXin Li        super(Cr50Test, self).initialize(host, cmdline_args)
68*9c5db199SXin Li
69*9c5db199SXin Li        if not hasattr(self, 'cr50'):
70*9c5db199SXin Li            raise error.TestNAError('Test can only be run on devices with '
71*9c5db199SXin Li                                    'access to the Cr50 console')
72*9c5db199SXin Li        # TODO(b/149948314): remove when dual-v4 is sorted out.
73*9c5db199SXin Li        if 'ccd' in self.servo.get_servo_version():
74*9c5db199SXin Li            self.servo.disable_ccd_watchdog_for_test()
75*9c5db199SXin Li
76*9c5db199SXin Li        logging.info('Test Args: %r', full_args)
77*9c5db199SXin Li
78*9c5db199SXin Li        self._devid = self.cr50.get_devid()
79*9c5db199SXin Li        self.can_set_ccd_level = (not self.servo.main_device_is_ccd()
80*9c5db199SXin Li                                  or self.cr50.testlab_is_on())
81*9c5db199SXin Li        self.original_ccd_level = self.cr50.get_ccd_level()
82*9c5db199SXin Li        self.original_ccd_settings = self.cr50.get_cap_dict(
83*9c5db199SXin Li                info=self.cr50.CAP_SETTING)
84*9c5db199SXin Li
85*9c5db199SXin Li        self.host = host
86*9c5db199SXin Li        # SSH commands should complete within 3 minutes. Change the default, so
87*9c5db199SXin Li        # it doesn't take half an hour for commands to timeout when the DUT is
88*9c5db199SXin Li        # down.
89*9c5db199SXin Li        self.host.set_default_run_timeout(180)
90*9c5db199SXin Li        tpm_utils.ClearTPMOwnerRequest(self.host, wait_for_ready=True)
91*9c5db199SXin Li        # Clear the FWMP, so it can't disable CCD.
92*9c5db199SXin Li        self.clear_fwmp()
93*9c5db199SXin Li
94*9c5db199SXin Li        # TODO(b/218492933) : find better way to disable rddkeepalive
95*9c5db199SXin Li        # Disable rddkeepalive, so the test can disable ccd.
96*9c5db199SXin Li        self.cr50.send_command('ccd testlab open')
97*9c5db199SXin Li        self.cr50.send_command('rddkeepalive disable')
98*9c5db199SXin Li        # faft-cr50 locks and reopens ccd. This will restrict some capabilities
99*9c5db199SXin Li        # c2d2 uses to control the duts. Set the capabilities to Always, so
100*9c5db199SXin Li        # individiual tests don't need to care that much.
101*9c5db199SXin Li        self.cr50.enable_servo_control_caps()
102*9c5db199SXin Li
103*9c5db199SXin Li        if self.can_set_ccd_level:
104*9c5db199SXin Li            # Lock cr50 so the console will be restricted
105*9c5db199SXin Li            self.cr50.set_ccd_level('lock')
106*9c5db199SXin Li        elif self.original_ccd_level != 'lock':
107*9c5db199SXin Li            raise error.TestNAError(
108*9c5db199SXin Li                    'Lock the console before running cr50 test')
109*9c5db199SXin Li
110*9c5db199SXin Li        self._save_original_state(full_args.get('release_path', ''))
111*9c5db199SXin Li
112*9c5db199SXin Li        # Try and download all images necessary to restore cr50 state.
113*9c5db199SXin Li        try:
114*9c5db199SXin Li            self._save_dbg_image(full_args.get('cr50_dbg_image_path', ''))
115*9c5db199SXin Li            self._saved_state |= self.DBG_IMAGE
116*9c5db199SXin Li        except Exception as e:
117*9c5db199SXin Li            logging.warning('Error saving DBG image: %s', str(e))
118*9c5db199SXin Li            if restore_cr50_image:
119*9c5db199SXin Li                raise error.TestNAError('Need DBG image: %s' % str(e))
120*9c5db199SXin Li
121*9c5db199SXin Li        try:
122*9c5db199SXin Li            self._save_eraseflashinfo_image(
123*9c5db199SXin Li                    full_args.get('cr50_eraseflashinfo_image_path', ''))
124*9c5db199SXin Li            if self.cr50.uses_board_property('BOARD_EC_CR50_COMM_SUPPORT'):
125*9c5db199SXin Li                raise error.TestError('Board cannot boot EFI image')
126*9c5db199SXin Li            self._saved_state |= self.ERASEFLASHINFO_IMAGE
127*9c5db199SXin Li        except Exception as e:
128*9c5db199SXin Li            logging.warning('Error saving eraseflashinfo image: %s', str(e))
129*9c5db199SXin Li            if restore_cr50_board_id:
130*9c5db199SXin Li                raise error.TestNAError(
131*9c5db199SXin Li                        'Need eraseflashinfo image: %s' % str(e))
132*9c5db199SXin Li
133*9c5db199SXin Li        # TODO(b/143888583): remove qual update during init once new design to
134*9c5db199SXin Li        # to provision cr50 updates is in place.
135*9c5db199SXin Li        # Make sure the release image is running before starting the test.
136*9c5db199SXin Li        is_release_qual = full_args.get('is_release_qual',
137*9c5db199SXin Li                                        '').lower() == 'true'
138*9c5db199SXin Li        if is_release_qual or self.running_cr50_release_suite():
139*9c5db199SXin Li            release_ver_arg = full_args.get('release_ver', '')
140*9c5db199SXin Li            release_path_arg = full_args.get('release_path', '')
141*9c5db199SXin Li            self.ensure_qual_image_is_running(release_ver_arg,
142*9c5db199SXin Li                                              release_path_arg)
143*9c5db199SXin Li
144*9c5db199SXin Li    def running_cr50_release_suite(self):
145*9c5db199SXin Li        """Return True if the DUT is in a release pool."""
146*9c5db199SXin Li        for pool in self.host.host_info_store.get().pools:
147*9c5db199SXin Li            # TODO(b/149109740): remove once the pool values are verified.
148*9c5db199SXin Li            # Change to run with faft-cr50 and faft-cr50-experimental suites.
149*9c5db199SXin Li            logging.info('Checking pool: %s', pool)
150*9c5db199SXin Li            if pool in self.RELEASE_POOLS:
151*9c5db199SXin Li                logging.info('Running a release test.')
152*9c5db199SXin Li                return True
153*9c5db199SXin Li        return False
154*9c5db199SXin Li
155*9c5db199SXin Li    def ensure_qual_image_is_running(self, qual_ver_str, qual_path):
156*9c5db199SXin Li        """Update to the qualification image if it's not running.
157*9c5db199SXin Li
158*9c5db199SXin Li        qual_ver_str and path are command line args that may be supplied to
159*9c5db199SXin Li        specify a local version or path. If neither are supplied, the version
160*9c5db199SXin Li        from gs will be used to determine what version cr50 should run.
161*9c5db199SXin Li
162*9c5db199SXin Li        qual_ver_str and qual_path should not be supplied together. If they are,
163*9c5db199SXin Li        the path will be used. It's not a big deal as long as they agree with
164*9c5db199SXin Li        each other.
165*9c5db199SXin Li
166*9c5db199SXin Li        @param qual_ver_str: qualification version string or None.
167*9c5db199SXin Li        @param qual_path: local path to the qualification image or None.
168*9c5db199SXin Li        """
169*9c5db199SXin Li        # Get the local image information.
170*9c5db199SXin Li        if qual_path:
171*9c5db199SXin Li            dest, qual_ver = cr50_utils.InstallImage(self.host, qual_path,
172*9c5db199SXin Li                                                     '/tmp/qual_cr50.bin')
173*9c5db199SXin Li            self.host.run('rm ' + dest)
174*9c5db199SXin Li            qual_bid_str = (cr50_utils.GetBoardIdInfoString(
175*9c5db199SXin Li                    qual_ver[2], False) if qual_ver[2] else '')
176*9c5db199SXin Li            qual_ver_str = '%s/%s' % (qual_ver[1], qual_bid_str)
177*9c5db199SXin Li
178*9c5db199SXin Li        # Determine the qualification version from.
179*9c5db199SXin Li        if not qual_ver_str:
180*9c5db199SXin Li            gsurl = os.path.join(self.GS_PRIVATE, self.CR50_QUAL_VERSION_FILE)
181*9c5db199SXin Li            dut_path = self.download_cr50_gs_file(gsurl, False)[1]
182*9c5db199SXin Li            qual_ver_str = self.host.run('cat ' + dut_path).stdout.strip()
183*9c5db199SXin Li
184*9c5db199SXin Li        # Download the qualification image based on the version.
185*9c5db199SXin Li        if not qual_path:
186*9c5db199SXin Li            rw, bid = qual_ver_str.split('/')
187*9c5db199SXin Li            qual_path, qual_ver = self.download_cr50_release_image(rw, bid)
188*9c5db199SXin Li
189*9c5db199SXin Li        logging.info('Cr50 Qual Version: %s', qual_ver_str)
190*9c5db199SXin Li        logging.info('Cr50 Qual Path: %s', qual_path)
191*9c5db199SXin Li        qual_chip_bid = cr50_utils.GetChipBIDFromImageBID(
192*9c5db199SXin Li                qual_ver[2], self.get_device_brand())
193*9c5db199SXin Li        logging.info('Cr50 Qual Chip BID: %s', qual_chip_bid)
194*9c5db199SXin Li
195*9c5db199SXin Li        # Replace only the prod or prepvt image based on the major version.
196*9c5db199SXin Li        if int(qual_ver[1].split('.')[1]) % 2:
197*9c5db199SXin Li            prod_ver = qual_ver
198*9c5db199SXin Li            prepvt_ver = self._original_image_state['prepvt_version']
199*9c5db199SXin Li            prod_path = qual_path
200*9c5db199SXin Li            prepvt_path = self._device_prepvt_image
201*9c5db199SXin Li        else:
202*9c5db199SXin Li            prod_ver = self._original_image_state['prod_version']
203*9c5db199SXin Li            prepvt_ver = qual_ver
204*9c5db199SXin Li            prod_path = self._device_prod_image
205*9c5db199SXin Li            prepvt_path = qual_path
206*9c5db199SXin Li
207*9c5db199SXin Li        # Generate a dictionary with all of the expected state.
208*9c5db199SXin Li        qual_state = {}
209*9c5db199SXin Li        qual_state['prod_version'] = prod_ver
210*9c5db199SXin Li        qual_state['prepvt_version'] = prepvt_ver
211*9c5db199SXin Li        qual_state['chip_bid'] = qual_chip_bid
212*9c5db199SXin Li        qual_state['running_image_bid'] = qual_ver[2]
213*9c5db199SXin Li        # The test can't rollback RO. The newest RO should be running at the end
214*9c5db199SXin Li        # of the test. max_ro will be none if the versions are the same. Use the
215*9c5db199SXin Li        # running_ro in that case.
216*9c5db199SXin Li        running_ro = self.get_saved_cr50_original_version()[0]
217*9c5db199SXin Li        max_ro = cr50_utils.GetNewestVersion(running_ro, qual_ver[0])
218*9c5db199SXin Li        qual_state['running_image_ver'] = (max_ro or running_ro, qual_ver[1],
219*9c5db199SXin Li                                           None)
220*9c5db199SXin Li        mismatch = self._check_running_image_and_board_id(qual_state)
221*9c5db199SXin Li        if not mismatch:
222*9c5db199SXin Li            logging.info('Running qual image. No update needed.')
223*9c5db199SXin Li            return
224*9c5db199SXin Li        logging.info('Cr50 qual update required.')
225*9c5db199SXin Li        self.make_rootfs_writable()
226*9c5db199SXin Li        self._update_device_images_and_running_cr50_firmware(
227*9c5db199SXin Li                qual_state, qual_path, prod_path, prepvt_path)
228*9c5db199SXin Li        logging.info("Recording qual device state as 'original' device state")
229*9c5db199SXin Li        self._save_original_state(qual_path)
230*9c5db199SXin Li
231*9c5db199SXin Li    def make_rootfs_writable(self):
232*9c5db199SXin Li        """Make rootfs writeable. Recover the dut if necessary."""
233*9c5db199SXin Li        path = None
234*9c5db199SXin Li        try:
235*9c5db199SXin Li            filesystem_util.make_rootfs_writable(self.host)
236*9c5db199SXin Li            return
237*9c5db199SXin Li        except error.AutoservRunError as e:
238*9c5db199SXin Li            if 'cannot remount' not in e.result_obj.stderr:
239*9c5db199SXin Li                raise
240*9c5db199SXin Li            path = e.result_obj.stderr.partition(
241*9c5db199SXin Li                    'cannot remount')[2].split()[0]
242*9c5db199SXin Li        # This shouldn't be possible.
243*9c5db199SXin Li        if not path:
244*9c5db199SXin Li            raise error.TestError('Need path to repair filesystem')
245*9c5db199SXin Li        logging.info('repair %s', path)
246*9c5db199SXin Li        # Repair the block. Assume yes to all questions. The exit status will be
247*9c5db199SXin Li        # 3, so ignore errors. make_rootfs_writable will fail if something
248*9c5db199SXin Li        # actually went wrong.
249*9c5db199SXin Li        self.host.run('e2fsck -y %s' % path, ignore_status=True)
250*9c5db199SXin Li        self.host.reboot()
251*9c5db199SXin Li        filesystem_util.make_rootfs_writable(self.host)
252*9c5db199SXin Li
253*9c5db199SXin Li    def _saved_cr50_state(self, state):
254*9c5db199SXin Li        """Returns True if the test has saved the given state
255*9c5db199SXin Li
256*9c5db199SXin Li        @param state: a integer representing the state to check.
257*9c5db199SXin Li        """
258*9c5db199SXin Li        return state & self._saved_state
259*9c5db199SXin Li
260*9c5db199SXin Li    def after_run_once(self):
261*9c5db199SXin Li        """Log which iteration just ran"""
262*9c5db199SXin Li        logging.info('successfully ran iteration %d', self.iteration)
263*9c5db199SXin Li        self._try_to_bring_dut_up()
264*9c5db199SXin Li
265*9c5db199SXin Li    def _save_dbg_image(self, cr50_dbg_image_path):
266*9c5db199SXin Li        """Save or download the node locked dev image.
267*9c5db199SXin Li
268*9c5db199SXin Li        @param cr50_dbg_image_path: The path to the node locked cr50 image.
269*9c5db199SXin Li        """
270*9c5db199SXin Li        if os.path.isfile(cr50_dbg_image_path):
271*9c5db199SXin Li            self._dbg_image_path = cr50_dbg_image_path
272*9c5db199SXin Li        else:
273*9c5db199SXin Li            self._dbg_image_path = self.download_cr50_debug_image()[0]
274*9c5db199SXin Li
275*9c5db199SXin Li    def _save_eraseflashinfo_image(self, cr50_eraseflashinfo_image_path):
276*9c5db199SXin Li        """Save or download the node locked eraseflashinfo image.
277*9c5db199SXin Li
278*9c5db199SXin Li        @param cr50_eraseflashinfo_image_path: The path to the node locked cr50
279*9c5db199SXin Li                                               image.
280*9c5db199SXin Li        """
281*9c5db199SXin Li        if os.path.isfile(cr50_eraseflashinfo_image_path):
282*9c5db199SXin Li            self._eraseflashinfo_image_path = cr50_eraseflashinfo_image_path
283*9c5db199SXin Li        else:
284*9c5db199SXin Li            self._eraseflashinfo_image_path = (
285*9c5db199SXin Li                    self.download_cr50_eraseflashinfo_image()[0])
286*9c5db199SXin Li
287*9c5db199SXin Li    def _save_device_image(self, ext):
288*9c5db199SXin Li        """Download the .prod or .prepvt device image and get the version.
289*9c5db199SXin Li
290*9c5db199SXin Li        @param ext: The Cr50 file extension: prod or prepvt.
291*9c5db199SXin Li        @returns (local_path, rw_version, bid_string) or (None, None, None) if
292*9c5db199SXin Li                 the file doesn't exist on the DUT.
293*9c5db199SXin Li        """
294*9c5db199SXin Li        version = self._original_image_state[ext + '_version']
295*9c5db199SXin Li        if not version:
296*9c5db199SXin Li            return None, None, None
297*9c5db199SXin Li        _, rw_ver, bid = version
298*9c5db199SXin Li        rw_filename = 'cr50.device.bin.%s.%s' % (ext, rw_ver)
299*9c5db199SXin Li        local_path = os.path.join(self.resultsdir, rw_filename)
300*9c5db199SXin Li        dut_path = cr50_utils.GetDevicePath(ext)
301*9c5db199SXin Li        self.host.get_file(dut_path, local_path)
302*9c5db199SXin Li        bid = cr50_utils.GetBoardIdInfoString(bid)
303*9c5db199SXin Li        return local_path, rw_ver, bid
304*9c5db199SXin Li
305*9c5db199SXin Li    def _save_original_images(self, release_path):
306*9c5db199SXin Li        """Use the saved state to find all of the device images.
307*9c5db199SXin Li
308*9c5db199SXin Li        This will download running cr50 image and the device image.
309*9c5db199SXin Li
310*9c5db199SXin Li        @param release_path: The release path given by test args
311*9c5db199SXin Li        """
312*9c5db199SXin Li        local_path, prod_rw, prod_bid = self._save_device_image('prod')
313*9c5db199SXin Li        self._device_prod_image = local_path
314*9c5db199SXin Li
315*9c5db199SXin Li        local_path, prepvt_rw, prepvt_bid = self._save_device_image('prepvt')
316*9c5db199SXin Li        self._device_prepvt_image = local_path
317*9c5db199SXin Li
318*9c5db199SXin Li        if os.path.isfile(release_path):
319*9c5db199SXin Li            self._original_cr50_image = release_path
320*9c5db199SXin Li            logging.info('using supplied image')
321*9c5db199SXin Li            return
322*9c5db199SXin Li        if self.tot_test_run:
323*9c5db199SXin Li            self._original_cr50_image = self.download_cr50_tot_image()
324*9c5db199SXin Li            return
325*9c5db199SXin Li
326*9c5db199SXin Li        # If the running cr50 image version matches the image on the DUT use
327*9c5db199SXin Li        # the DUT image as the original image. If the versions don't match
328*9c5db199SXin Li        # download the image from google storage
329*9c5db199SXin Li        _, running_rw, running_bid = self.get_saved_cr50_original_version()
330*9c5db199SXin Li
331*9c5db199SXin Li        # Convert the running board id to the same format as the prod and
332*9c5db199SXin Li        # prepvt board ids.
333*9c5db199SXin Li        running_bid = cr50_utils.GetBoardIdInfoString(running_bid)
334*9c5db199SXin Li        if running_rw == prod_rw and running_bid == prod_bid:
335*9c5db199SXin Li            logging.info('Using device cr50 prod image %s %s', prod_rw,
336*9c5db199SXin Li                         prod_bid)
337*9c5db199SXin Li            self._original_cr50_image = self._device_prod_image
338*9c5db199SXin Li        elif running_rw == prepvt_rw and running_bid == prepvt_bid:
339*9c5db199SXin Li            logging.info('Using device cr50 prepvt image %s %s', prepvt_rw,
340*9c5db199SXin Li                         prepvt_bid)
341*9c5db199SXin Li            self._original_cr50_image = self._device_prepvt_image
342*9c5db199SXin Li        else:
343*9c5db199SXin Li            logging.info('Downloading cr50 image %s %s', running_rw,
344*9c5db199SXin Li                         running_bid)
345*9c5db199SXin Li            self._original_cr50_image = self.download_cr50_release_image(
346*9c5db199SXin Li                    running_rw, running_bid)[0]
347*9c5db199SXin Li
348*9c5db199SXin Li    def _save_original_state(self, release_path):
349*9c5db199SXin Li        """Save the cr50 related state.
350*9c5db199SXin Li
351*9c5db199SXin Li        Save the device's current cr50 version, cr50 board id, the running cr50
352*9c5db199SXin Li        image, the prepvt, and prod cr50 images. These will be used to restore
353*9c5db199SXin Li        the cr50 state during cleanup.
354*9c5db199SXin Li
355*9c5db199SXin Li        @param release_path: the optional command line arg of path for the local
356*9c5db199SXin Li                             cr50 image.
357*9c5db199SXin Li        """
358*9c5db199SXin Li        self._saved_state &= ~self.INITIAL_IMAGE_STATE
359*9c5db199SXin Li        self._original_image_state = self.get_image_and_bid_state()
360*9c5db199SXin Li        # We successfully saved the device state
361*9c5db199SXin Li        self._saved_state |= self.INITIAL_IMAGE_STATE
362*9c5db199SXin Li        self._saved_state &= ~self.DEVICE_IMAGES
363*9c5db199SXin Li        try:
364*9c5db199SXin Li            self._save_original_images(release_path)
365*9c5db199SXin Li            self._saved_state |= self.DEVICE_IMAGES
366*9c5db199SXin Li        except Exception as e:
367*9c5db199SXin Li            logging.warning('Error saving ChromeOS image cr50 firmware: %s',
368*9c5db199SXin Li                            str(e))
369*9c5db199SXin Li
370*9c5db199SXin Li    def get_saved_cr50_original_version(self):
371*9c5db199SXin Li        """Return (ro ver, rw ver, bid)."""
372*9c5db199SXin Li        if ('running_image_ver' not in self._original_image_state
373*9c5db199SXin Li                    or 'running_image_bid' not in self._original_image_state):
374*9c5db199SXin Li            raise error.TestError('No record of original cr50 image version')
375*9c5db199SXin Li        return (self._original_image_state['running_image_ver'][0],
376*9c5db199SXin Li                self._original_image_state['running_image_ver'][1],
377*9c5db199SXin Li                self._original_image_state['running_image_bid'])
378*9c5db199SXin Li
379*9c5db199SXin Li    def get_saved_cr50_original_path(self):
380*9c5db199SXin Li        """Return the local path for the original cr50 image."""
381*9c5db199SXin Li        if not hasattr(self, '_original_cr50_image'):
382*9c5db199SXin Li            raise error.TestError('No record of original image')
383*9c5db199SXin Li        return self._original_cr50_image
384*9c5db199SXin Li
385*9c5db199SXin Li    def has_saved_dbg_image_path(self):
386*9c5db199SXin Li        """Returns true if we saved the node locked debug image."""
387*9c5db199SXin Li        return hasattr(self, '_dbg_image_path')
388*9c5db199SXin Li
389*9c5db199SXin Li    def get_saved_dbg_image_path(self):
390*9c5db199SXin Li        """Return the local path for the cr50 dev image."""
391*9c5db199SXin Li        if not self.has_saved_dbg_image_path():
392*9c5db199SXin Li            raise error.TestError('No record of debug image')
393*9c5db199SXin Li        return self._dbg_image_path
394*9c5db199SXin Li
395*9c5db199SXin Li    def get_saved_eraseflashinfo_image_path(self):
396*9c5db199SXin Li        """Return the local path for the cr50 eraseflashinfo image."""
397*9c5db199SXin Li        if not hasattr(self, '_eraseflashinfo_image_path'):
398*9c5db199SXin Li            raise error.TestError('No record of eraseflashinfo image')
399*9c5db199SXin Li        return self._eraseflashinfo_image_path
400*9c5db199SXin Li
401*9c5db199SXin Li    def get_device_brand(self):
402*9c5db199SXin Li        """Returns the 4 character device brand."""
403*9c5db199SXin Li        return self._original_image_state['cros_config / brand-code']
404*9c5db199SXin Li
405*9c5db199SXin Li    def _retry_cr50_update(self, image, retries, rollback):
406*9c5db199SXin Li        """Try to update to the given image retries amount of times.
407*9c5db199SXin Li
408*9c5db199SXin Li        @param image: The image path.
409*9c5db199SXin Li        @param retries: The number of times to try to update.
410*9c5db199SXin Li        @param rollback: Run rollback after the update.
411*9c5db199SXin Li        @raises TestFail if the update failed.
412*9c5db199SXin Li        """
413*9c5db199SXin Li        for i in range(retries):
414*9c5db199SXin Li            try:
415*9c5db199SXin Li                return self.cr50_update(image, rollback=rollback)
416*9c5db199SXin Li            except Exception as e:
417*9c5db199SXin Li                logging.warning('Failed to update to %s attempt %d: %s',
418*9c5db199SXin Li                                os.path.basename(image), i, str(e))
419*9c5db199SXin Li                logging.info('Sleeping 60 seconds')
420*9c5db199SXin Li                time.sleep(60)
421*9c5db199SXin Li        raise error.TestError(
422*9c5db199SXin Li                'Failed to update to %s' % os.path.basename(image))
423*9c5db199SXin Li
424*9c5db199SXin Li    def run_update_to_eraseflashinfo(self):
425*9c5db199SXin Li        """Erase flashinfo using the eraseflashinfo image.
426*9c5db199SXin Li
427*9c5db199SXin Li        Update to the DBG image, rollback to the eraseflashinfo, and run
428*9c5db199SXin Li        eraseflashinfo.
429*9c5db199SXin Li        """
430*9c5db199SXin Li        self._retry_cr50_update(self._dbg_image_path, 3, False)
431*9c5db199SXin Li        self._retry_cr50_update(self._eraseflashinfo_image_path, 3, True)
432*9c5db199SXin Li        if not self.cr50.eraseflashinfo():
433*9c5db199SXin Li            raise error.TestError('Unable to erase the board id')
434*9c5db199SXin Li
435*9c5db199SXin Li    def eraseflashinfo_and_restore_image(self, image=''):
436*9c5db199SXin Li        """eraseflashinfo and update to the given the image.
437*9c5db199SXin Li
438*9c5db199SXin Li        @param image: the image to end on. Use the original test image if no
439*9c5db199SXin Li                      image is given.
440*9c5db199SXin Li        """
441*9c5db199SXin Li        image = image if image else self.get_saved_cr50_original_path()
442*9c5db199SXin Li        self.run_update_to_eraseflashinfo()
443*9c5db199SXin Li        self.cr50_update(image)
444*9c5db199SXin Li
445*9c5db199SXin Li    def update_cr50_image_and_board_id(self, image_path, bid):
446*9c5db199SXin Li        """Set the chip board id and updating the cr50 image.
447*9c5db199SXin Li
448*9c5db199SXin Li        Make 3 attempts to update to the original image. Use a rollback from
449*9c5db199SXin Li        the DBG image to erase the state that can only be erased by a DBG image.
450*9c5db199SXin Li        Set the chip board id during rollback.
451*9c5db199SXin Li
452*9c5db199SXin Li        @param image_path: path of the image to update to.
453*9c5db199SXin Li        @param bid: the board id to set.
454*9c5db199SXin Li        """
455*9c5db199SXin Li        current_bid = cr50_utils.GetChipBoardId(self.host)
456*9c5db199SXin Li        bid_mismatch = current_bid != bid
457*9c5db199SXin Li        set_bid = bid_mismatch and bid != cr50_utils.ERASED_CHIP_BID
458*9c5db199SXin Li        bid_is_erased = current_bid == cr50_utils.ERASED_CHIP_BID
459*9c5db199SXin Li        eraseflashinfo = bid_mismatch and not bid_is_erased
460*9c5db199SXin Li
461*9c5db199SXin Li        if (eraseflashinfo
462*9c5db199SXin Li                    and not self._saved_cr50_state(self.ERASEFLASHINFO_IMAGE)):
463*9c5db199SXin Li            raise error.TestFail('Did not save eraseflashinfo image')
464*9c5db199SXin Li
465*9c5db199SXin Li        # Remove prepvt and prod iamges, so they don't interfere with the test
466*9c5db199SXin Li        # rolling back and updating to images that my be older than the images
467*9c5db199SXin Li        # on the device.
468*9c5db199SXin Li        if filesystem_util.is_rootfs_writable(self.host):
469*9c5db199SXin Li            self.host.run('rm %s' % cr50_utils.CR50_PREPVT, ignore_status=True)
470*9c5db199SXin Li            self.host.run('rm %s' % cr50_utils.CR50_PROD, ignore_status=True)
471*9c5db199SXin Li
472*9c5db199SXin Li        if eraseflashinfo:
473*9c5db199SXin Li            self.run_update_to_eraseflashinfo()
474*9c5db199SXin Li
475*9c5db199SXin Li        self._retry_cr50_update(self._dbg_image_path, 3, False)
476*9c5db199SXin Li
477*9c5db199SXin Li        chip_bid = bid[0]
478*9c5db199SXin Li        chip_flags = bid[2]
479*9c5db199SXin Li        if set_bid:
480*9c5db199SXin Li            self.cr50.set_board_id(chip_bid, chip_flags)
481*9c5db199SXin Li
482*9c5db199SXin Li        self._retry_cr50_update(image_path, 3, True)
483*9c5db199SXin Li
484*9c5db199SXin Li    def _discharging_factory_mode_cleanup(self):
485*9c5db199SXin Li        """Try to get the dut back into charging mode.
486*9c5db199SXin Li
487*9c5db199SXin Li        Shutdown the DUT, fake disconnect AC, and then turn on the DUT to
488*9c5db199SXin Li        try to recover the EC.
489*9c5db199SXin Li
490*9c5db199SXin Li        When Cr50 enters factory mode on Wilco, the EC disables charging.
491*9c5db199SXin Li        Try to run the sequence to get the Wilco EC out of the factory mode
492*9c5db199SXin Li        state, so it reenables charging.
493*9c5db199SXin Li        """
494*9c5db199SXin Li        if self.faft_config.chrome_ec:
495*9c5db199SXin Li            return
496*9c5db199SXin Li        charge_state = self.host.get_power_supply_info()['Battery']['state']
497*9c5db199SXin Li        logging.info('Charge state: %r', charge_state)
498*9c5db199SXin Li        if 'Discharging' not in charge_state:
499*9c5db199SXin Li            logging.info('Charge state is ok')
500*9c5db199SXin Li            return
501*9c5db199SXin Li
502*9c5db199SXin Li        if not self.servo.is_servo_v4_type_c():
503*9c5db199SXin Li            raise error.TestError(
504*9c5db199SXin Li                    'Cannot recover charging without Type C servo')
505*9c5db199SXin Li        # Disconnect the charger and reset the dut to recover charging.
506*9c5db199SXin Li        logging.info('Recovering charging')
507*9c5db199SXin Li        self.faft_client.system.run_shell_command('poweroff')
508*9c5db199SXin Li        time.sleep(self.cr50.SHORT_WAIT)
509*9c5db199SXin Li        self.servo.set_nocheck('servo_v4_uart_cmd', 'fakedisconnect 100 20000')
510*9c5db199SXin Li        time.sleep(self.cr50.SHORT_WAIT)
511*9c5db199SXin Li        self._try_to_bring_dut_up()
512*9c5db199SXin Li        charge_state = self.host.get_power_supply_info()['Battery']['state']
513*9c5db199SXin Li        logging.info('Charge state: %r', charge_state)
514*9c5db199SXin Li        if 'Discharging' in charge_state:
515*9c5db199SXin Li            logging.warning('DUT still discharging')
516*9c5db199SXin Li
517*9c5db199SXin Li    def _cleanup_required(self, state_mismatch, image_type):
518*9c5db199SXin Li        """Return True if the update can fix something in the mismatched state.
519*9c5db199SXin Li
520*9c5db199SXin Li        @param state_mismatch: a dictionary of the mismatched state.
521*9c5db199SXin Li        @param image_type: The integer representing the type of image
522*9c5db199SXin Li        """
523*9c5db199SXin Li        state_image_restores = set(self.STATE_IMAGE_RESTORES[image_type])
524*9c5db199SXin Li        restore = state_image_restores.intersection(state_mismatch.keys())
525*9c5db199SXin Li        if restore and not self._saved_cr50_state(image_type):
526*9c5db199SXin Li            raise error.TestError(
527*9c5db199SXin Li                    'Did not save images to restore %s' % (', '.join(restore)))
528*9c5db199SXin Li        return not not restore
529*9c5db199SXin Li
530*9c5db199SXin Li    def _get_image_information(self, ext):
531*9c5db199SXin Li        """Get the image information for the .prod or .prepvt image.
532*9c5db199SXin Li
533*9c5db199SXin Li        @param ext: The extension string prod or prepvt
534*9c5db199SXin Li        @param returns: The image version or None if the image doesn't exist.
535*9c5db199SXin Li        """
536*9c5db199SXin Li        dut_path = cr50_utils.GetDevicePath(ext)
537*9c5db199SXin Li        file_exists = self.host.path_exists(dut_path)
538*9c5db199SXin Li        if file_exists:
539*9c5db199SXin Li            return cr50_utils.GetBinVersion(self.host, dut_path)
540*9c5db199SXin Li        return None
541*9c5db199SXin Li
542*9c5db199SXin Li    def get_image_and_bid_state(self):
543*9c5db199SXin Li        """Get a dict with the current device cr50 information.
544*9c5db199SXin Li
545*9c5db199SXin Li        The state dict will include the platform brand, chip board id, the
546*9c5db199SXin Li        running cr50 image version, the running cr50 image board id, and the
547*9c5db199SXin Li        device cr50 image version.
548*9c5db199SXin Li        """
549*9c5db199SXin Li        state = {}
550*9c5db199SXin Li        state['cros_config / brand-code'] = self.host.run(
551*9c5db199SXin Li                'cros_config / brand-code', ignore_status=True).stdout.strip()
552*9c5db199SXin Li        state['prod_version'] = self._get_image_information('prod')
553*9c5db199SXin Li        state['prepvt_version'] = self._get_image_information('prepvt')
554*9c5db199SXin Li        state['chip_bid'] = cr50_utils.GetChipBoardId(self.host)
555*9c5db199SXin Li        state['chip_bid_str'] = '%08x:%08x:%08x' % state['chip_bid']
556*9c5db199SXin Li        state['running_image_ver'] = cr50_utils.GetRunningVersion(self.host)
557*9c5db199SXin Li        state['running_image_bid'] = self.cr50.get_active_board_id_str()
558*9c5db199SXin Li
559*9c5db199SXin Li        logging.debug('Current Cr50 state:\n%s', pprint.pformat(state))
560*9c5db199SXin Li        return state
561*9c5db199SXin Li
562*9c5db199SXin Li    def _check_running_image_and_board_id(self, expected_state):
563*9c5db199SXin Li        """Compare the current image and board id to the given state.
564*9c5db199SXin Li
565*9c5db199SXin Li        @param expected_state: A dictionary of the state to compare to.
566*9c5db199SXin Li        @return: A dictionary with the state that is wrong as the key and the
567*9c5db199SXin Li                 expected and current state as the value.
568*9c5db199SXin Li        """
569*9c5db199SXin Li        if not (self._saved_state & self.INITIAL_IMAGE_STATE):
570*9c5db199SXin Li            logging.warning(
571*9c5db199SXin Li                    'Did not save the original state. Cannot verify it '
572*9c5db199SXin Li                    'matches')
573*9c5db199SXin Li            return
574*9c5db199SXin Li        # Make sure the /var/cache/cr50* state is up to date.
575*9c5db199SXin Li        cr50_utils.ClearUpdateStateAndReboot(self.host)
576*9c5db199SXin Li
577*9c5db199SXin Li        mismatch = {}
578*9c5db199SXin Li        state = self.get_image_and_bid_state()
579*9c5db199SXin Li
580*9c5db199SXin Li        for k, expected_val in six.iteritems(expected_state):
581*9c5db199SXin Li            val = state[k]
582*9c5db199SXin Li            if val != expected_val:
583*9c5db199SXin Li                mismatch[k] = 'expected: %s, current: %s' % (expected_val, val)
584*9c5db199SXin Li
585*9c5db199SXin Li        if mismatch:
586*9c5db199SXin Li            logging.warning('State Mismatch:\n%s', pprint.pformat(mismatch))
587*9c5db199SXin Li        return mismatch
588*9c5db199SXin Li
589*9c5db199SXin Li    def _check_original_image_state(self):
590*9c5db199SXin Li        """Compare the current cr50 state to the original state.
591*9c5db199SXin Li
592*9c5db199SXin Li        @return: A dictionary with the state that is wrong as the key and the
593*9c5db199SXin Li                 new and old state as the value
594*9c5db199SXin Li        """
595*9c5db199SXin Li        mismatch = self._check_running_image_and_board_id(
596*9c5db199SXin Li                self._original_image_state)
597*9c5db199SXin Li        if not mismatch:
598*9c5db199SXin Li            logging.info('The device is in the original state')
599*9c5db199SXin Li        return mismatch
600*9c5db199SXin Li
601*9c5db199SXin Li    def _reset_ccd_settings(self):
602*9c5db199SXin Li        """Reset the ccd lock and capability states."""
603*9c5db199SXin Li        if not self.cr50.ccd_is_reset():
604*9c5db199SXin Li            # Try to open cr50 and enable testlab mode if it isn't enabled.
605*9c5db199SXin Li            try:
606*9c5db199SXin Li                self.fast_ccd_open(True)
607*9c5db199SXin Li            except:
608*9c5db199SXin Li                # Even if we can't open cr50, do our best to reset the rest of
609*9c5db199SXin Li                # the system state. Log a warning here.
610*9c5db199SXin Li                logging.warning('Unable to Open cr50', exc_info=True)
611*9c5db199SXin Li            self.cr50.ccd_reset(servo_en=False)
612*9c5db199SXin Li            if not self.cr50.ccd_is_reset():
613*9c5db199SXin Li                raise error.TestFail('Could not reset ccd')
614*9c5db199SXin Li
615*9c5db199SXin Li        current_settings = self.cr50.get_cap_dict(info=self.cr50.CAP_SETTING)
616*9c5db199SXin Li        if self.original_ccd_settings != current_settings:
617*9c5db199SXin Li            if not self.can_set_ccd_level:
618*9c5db199SXin Li                raise error.TestError("CCD state has changed, but we can't "
619*9c5db199SXin Li                                      "restore it")
620*9c5db199SXin Li            self.fast_ccd_open(True)
621*9c5db199SXin Li            self.cr50.set_caps(self.original_ccd_settings)
622*9c5db199SXin Li
623*9c5db199SXin Li        # First try using testlab open to open the device
624*9c5db199SXin Li        if self.original_ccd_level == 'open':
625*9c5db199SXin Li            self.fast_ccd_open(True)
626*9c5db199SXin Li        elif self.original_ccd_level != self.cr50.get_ccd_level():
627*9c5db199SXin Li            self.cr50.set_ccd_level(self.original_ccd_level)
628*9c5db199SXin Li
629*9c5db199SXin Li    def fast_ccd_open(self,
630*9c5db199SXin Li                      enable_testlab=False,
631*9c5db199SXin Li                      reset_ccd=True,
632*9c5db199SXin Li                      dev_mode=False):
633*9c5db199SXin Li        """Check for watchdog resets after opening ccd.
634*9c5db199SXin Li
635*9c5db199SXin Li        Args:
636*9c5db199SXin Li            enable_testlab: If True, enable testlab mode after cr50 is open.
637*9c5db199SXin Li            reset_ccd: If True, reset ccd after open.
638*9c5db199SXin Li            dev_mode: True if the device should be in dev mode after ccd is
639*9c5db199SXin Li                      is opened.
640*9c5db199SXin Li        """
641*9c5db199SXin Li        try:
642*9c5db199SXin Li            super(Cr50Test, self).fast_ccd_open(enable_testlab, reset_ccd,
643*9c5db199SXin Li                                                dev_mode)
644*9c5db199SXin Li        except Exception as e:
645*9c5db199SXin Li            # Check for cr50 watchdog resets.
646*9c5db199SXin Li            self.cr50.check_for_console_errors('Fast ccd open')
647*9c5db199SXin Li            raise
648*9c5db199SXin Li
649*9c5db199SXin Li    def cleanup(self):
650*9c5db199SXin Li        """Attempt to cleanup the cr50 state. Then run firmware cleanup"""
651*9c5db199SXin Li        try:
652*9c5db199SXin Li            # Reset the password as the first thing in cleanup. It is important
653*9c5db199SXin Li            # that if some other part of cleanup fails, the password has at
654*9c5db199SXin Li            # least been reset.
655*9c5db199SXin Li            # DO NOT PUT ANYTHING BEFORE THIS.
656*9c5db199SXin Li            self._try_quick_ccd_cleanup()
657*9c5db199SXin Li
658*9c5db199SXin Li            self.servo.enable_main_servo_device()
659*9c5db199SXin Li
660*9c5db199SXin Li            self._try_to_bring_dut_up()
661*9c5db199SXin Li            self._restore_cr50_state()
662*9c5db199SXin Li
663*9c5db199SXin Li            # Make sure the sarien EC isn't stuck in factory mode.
664*9c5db199SXin Li            self._discharging_factory_mode_cleanup()
665*9c5db199SXin Li        finally:
666*9c5db199SXin Li            super(Cr50Test, self).cleanup()
667*9c5db199SXin Li
668*9c5db199SXin Li        # Check the logs captured during firmware_test cleanup for cr50 errors.
669*9c5db199SXin Li        self.cr50.check_for_console_errors('Test Cleanup')
670*9c5db199SXin Li        self.servo.allow_ccd_watchdog_for_test()
671*9c5db199SXin Li
672*9c5db199SXin Li    def _update_device_images_and_running_cr50_firmware(
673*9c5db199SXin Li            self, state, release_path, prod_path, prepvt_path):
674*9c5db199SXin Li        """Update cr50, set the board id, and copy firmware to the DUT.
675*9c5db199SXin Li
676*9c5db199SXin Li        @param state: A dictionary with the expected running version, board id,
677*9c5db199SXin Li                      device cr50 firmware versions.
678*9c5db199SXin Li        @param release_path: The image to update cr50 to
679*9c5db199SXin Li        @param prod_path: The path to the .prod image
680*9c5db199SXin Li        @param prepvt_path: The path to the .prepvt image
681*9c5db199SXin Li        @raises TestError: if setting any state failed
682*9c5db199SXin Li        """
683*9c5db199SXin Li        mismatch = self._check_running_image_and_board_id(state)
684*9c5db199SXin Li        if not mismatch:
685*9c5db199SXin Li            logging.info('Nothing to do.')
686*9c5db199SXin Li            return
687*9c5db199SXin Li
688*9c5db199SXin Li        # Use the DBG image to restore the original image.
689*9c5db199SXin Li        if self._cleanup_required(mismatch, self.DBG_IMAGE):
690*9c5db199SXin Li            self.update_cr50_image_and_board_id(release_path,
691*9c5db199SXin Li                                                state['chip_bid'])
692*9c5db199SXin Li
693*9c5db199SXin Li        self._try_to_bring_dut_up()
694*9c5db199SXin Li        new_mismatch = self._check_running_image_and_board_id(state)
695*9c5db199SXin Li        # Copy the original .prod and .prepvt images back onto the DUT.
696*9c5db199SXin Li        if (self._cleanup_required(new_mismatch, self.DEVICE_IMAGES)
697*9c5db199SXin Li                    and filesystem_util.is_rootfs_writable(self.host)):
698*9c5db199SXin Li            # Copy the .prod file onto the DUT.
699*9c5db199SXin Li            if prod_path and 'prod_version' in new_mismatch:
700*9c5db199SXin Li                cr50_utils.InstallImage(self.host, prod_path,
701*9c5db199SXin Li                                        cr50_utils.CR50_PROD)
702*9c5db199SXin Li            # Copy the .prepvt file onto the DUT.
703*9c5db199SXin Li            if prepvt_path and 'prepvt_version' in new_mismatch:
704*9c5db199SXin Li                cr50_utils.InstallImage(self.host, prepvt_path,
705*9c5db199SXin Li                                        cr50_utils.CR50_PREPVT)
706*9c5db199SXin Li
707*9c5db199SXin Li        final_mismatch = self._check_running_image_and_board_id(state)
708*9c5db199SXin Li        if final_mismatch:
709*9c5db199SXin Li            raise error.TestError(
710*9c5db199SXin Li                    'Could not update cr50 image state: %s' % final_mismatch)
711*9c5db199SXin Li        logging.info('Successfully updated all device cr50 firmware state.')
712*9c5db199SXin Li
713*9c5db199SXin Li    def _restore_device_images_and_running_cr50_firmware(self):
714*9c5db199SXin Li        """Restore the images on the device and the running cr50 image."""
715*9c5db199SXin Li        if self._provision_update:
716*9c5db199SXin Li            return
717*9c5db199SXin Li        mismatch = self._check_original_image_state()
718*9c5db199SXin Li        if not mismatch:
719*9c5db199SXin Li            return
720*9c5db199SXin Li        self._update_device_images_and_running_cr50_firmware(
721*9c5db199SXin Li                self._original_image_state,
722*9c5db199SXin Li                self.get_saved_cr50_original_path(), self._device_prod_image,
723*9c5db199SXin Li                self._device_prepvt_image)
724*9c5db199SXin Li
725*9c5db199SXin Li        if self._raise_error_on_mismatch and mismatch:
726*9c5db199SXin Li            raise error.TestError('Unexpected state mismatch during '
727*9c5db199SXin Li                                  'cleanup %s' % mismatch)
728*9c5db199SXin Li
729*9c5db199SXin Li    def _try_quick_ccd_cleanup(self):
730*9c5db199SXin Li        """Try to clear all ccd state."""
731*9c5db199SXin Li        # This is just a first pass at cleanup. Don't raise any errors.
732*9c5db199SXin Li        try:
733*9c5db199SXin Li            self.cr50.ccd_enable()
734*9c5db199SXin Li        except Exception as e:
735*9c5db199SXin Li            logging.warning('Ignored exception enabling ccd %r', str(e))
736*9c5db199SXin Li        self.cr50.send_command('ccd testlab open')
737*9c5db199SXin Li        self.cr50.send_command('rddkeepalive disable')
738*9c5db199SXin Li        self.cr50.ccd_reset()
739*9c5db199SXin Li        self.cr50.send_command('wp follow_batt_pres atboot')
740*9c5db199SXin Li
741*9c5db199SXin Li    def _restore_ccd_settings(self):
742*9c5db199SXin Li        """Restore the original ccd state."""
743*9c5db199SXin Li        self._try_quick_ccd_cleanup()
744*9c5db199SXin Li
745*9c5db199SXin Li        # Reboot cr50 if the console is accessible. This will reset most state.
746*9c5db199SXin Li        if self.cr50.get_cap('GscFullConsole')[self.cr50.CAP_IS_ACCESSIBLE]:
747*9c5db199SXin Li            self.cr50.reboot()
748*9c5db199SXin Li
749*9c5db199SXin Li        # Reenable servo v4 CCD
750*9c5db199SXin Li        self.cr50.ccd_enable()
751*9c5db199SXin Li
752*9c5db199SXin Li        # reboot to normal mode if the device is in dev mode.
753*9c5db199SXin Li        self.enter_mode_after_checking_cr50_state('normal')
754*9c5db199SXin Li
755*9c5db199SXin Li        self._try_to_bring_dut_up()
756*9c5db199SXin Li        tpm_utils.ClearTPMOwnerRequest(self.host, wait_for_ready=True)
757*9c5db199SXin Li        self.clear_fwmp()
758*9c5db199SXin Li
759*9c5db199SXin Li        # Restore the ccd privilege level
760*9c5db199SXin Li        self._reset_ccd_settings()
761*9c5db199SXin Li
762*9c5db199SXin Li    def _restore_cr50_state(self):
763*9c5db199SXin Li        """Restore cr50 state, so the device can be used for further testing.
764*9c5db199SXin Li
765*9c5db199SXin Li        Restore the cr50 image and board id first. Then CCD, because flashing
766*9c5db199SXin Li        dev signed images completely clears the CCD state.
767*9c5db199SXin Li        """
768*9c5db199SXin Li        try:
769*9c5db199SXin Li            self._restore_device_images_and_running_cr50_firmware()
770*9c5db199SXin Li        except Exception as e:
771*9c5db199SXin Li            logging.warning('Issue restoring Cr50 image: %s', str(e))
772*9c5db199SXin Li            raise
773*9c5db199SXin Li        finally:
774*9c5db199SXin Li            self._restore_ccd_settings()
775*9c5db199SXin Li
776*9c5db199SXin Li    def find_cr50_gs_image(self, gsurl):
777*9c5db199SXin Li        """Find the cr50 gs image name.
778*9c5db199SXin Li
779*9c5db199SXin Li        @param gsurl: the cr50 image location
780*9c5db199SXin Li        @return: a list of the gsutil bucket, filename or None if the file
781*9c5db199SXin Li                 can't be found
782*9c5db199SXin Li        """
783*9c5db199SXin Li        try:
784*9c5db199SXin Li            return utils.gs_ls(gsurl)[0].rsplit('/', 1)
785*9c5db199SXin Li        except error.CmdError:
786*9c5db199SXin Li            logging.info('%s does not exist', gsurl)
787*9c5db199SXin Li            return None
788*9c5db199SXin Li
789*9c5db199SXin Li    def _extract_cr50_image(self, archive, fn):
790*9c5db199SXin Li        """Extract the filename from the given archive
791*9c5db199SXin Li        Aargs:
792*9c5db199SXin Li            archive: the archive location on the host
793*9c5db199SXin Li            fn: the file to extract
794*9c5db199SXin Li
795*9c5db199SXin Li        Returns:
796*9c5db199SXin Li            The location of the extracted file
797*9c5db199SXin Li        """
798*9c5db199SXin Li        remote_dir = os.path.dirname(archive)
799*9c5db199SXin Li        result = self.host.run('tar xfv %s -C %s' % (archive, remote_dir))
800*9c5db199SXin Li        for line in result.stdout.splitlines():
801*9c5db199SXin Li            if os.path.basename(line) == fn:
802*9c5db199SXin Li                return os.path.join(remote_dir, line)
803*9c5db199SXin Li        raise error.TestFail('%s was not extracted from %s' % (fn, archive))
804*9c5db199SXin Li
805*9c5db199SXin Li    def download_cr50_gs_file(self, gsurl, extract_fn):
806*9c5db199SXin Li        """Download and extract the file at gsurl.
807*9c5db199SXin Li
808*9c5db199SXin Li        @param gsurl: The gs url for the cr50 image
809*9c5db199SXin Li        @param extract_fn: The name of the file to extract from the cr50 image
810*9c5db199SXin Li                        tarball. Don't extract anything if extract_fn is None.
811*9c5db199SXin Li        @return: a tuple (local path, host path)
812*9c5db199SXin Li        """
813*9c5db199SXin Li        file_info = self.find_cr50_gs_image(gsurl)
814*9c5db199SXin Li        if not file_info:
815*9c5db199SXin Li            raise error.TestFail('Could not find %s' % gsurl)
816*9c5db199SXin Li        bucket, fn = file_info
817*9c5db199SXin Li
818*9c5db199SXin Li        remote_temp_dir = '/tmp/'
819*9c5db199SXin Li        src = os.path.join(remote_temp_dir, fn)
820*9c5db199SXin Li        dest = os.path.join(self.resultsdir, fn)
821*9c5db199SXin Li
822*9c5db199SXin Li        # Copy the image to the dut
823*9c5db199SXin Li        gsutil_wrapper.copy_private_bucket(
824*9c5db199SXin Li                host=self.host,
825*9c5db199SXin Li                bucket=bucket,
826*9c5db199SXin Li                filename=fn,
827*9c5db199SXin Li                destination=remote_temp_dir)
828*9c5db199SXin Li        if extract_fn:
829*9c5db199SXin Li            src = self._extract_cr50_image(src, extract_fn)
830*9c5db199SXin Li            logging.info('extracted %s', src)
831*9c5db199SXin Li            # Remove .tbz2 from the local path.
832*9c5db199SXin Li            dest = os.path.splitext(dest)[0]
833*9c5db199SXin Li
834*9c5db199SXin Li        self.host.get_file(src, dest)
835*9c5db199SXin Li        return dest, src
836*9c5db199SXin Li
837*9c5db199SXin Li    def download_cr50_gs_image(self, gsurl, extract_fn, image_bid):
838*9c5db199SXin Li        """Get the image from gs and save it in the autotest dir.
839*9c5db199SXin Li
840*9c5db199SXin Li        @param gsurl: The gs url for the cr50 image
841*9c5db199SXin Li        @param extract_fn: The name of the file to extract from the cr50 image
842*9c5db199SXin Li                        tarball. Don't extract anything if extract_fn is None.
843*9c5db199SXin Li        @param image_bid: the image symbolic board id
844*9c5db199SXin Li        @return: A tuple with the local path and version
845*9c5db199SXin Li        """
846*9c5db199SXin Li        dest, src = self.download_cr50_gs_file(gsurl, extract_fn)
847*9c5db199SXin Li        ver = cr50_utils.GetBinVersion(self.host, src)
848*9c5db199SXin Li
849*9c5db199SXin Li        # Compare the image board id to the downloaded image to make sure we got
850*9c5db199SXin Li        # the right file
851*9c5db199SXin Li        downloaded_bid = cr50_utils.GetBoardIdInfoString(ver[2], symbolic=True)
852*9c5db199SXin Li        if image_bid and image_bid != downloaded_bid:
853*9c5db199SXin Li            raise error.TestError(
854*9c5db199SXin Li                    'Could not download image with matching '
855*9c5db199SXin Li                    'board id wanted %s got %s' % (image_bid, downloaded_bid))
856*9c5db199SXin Li        return dest, ver
857*9c5db199SXin Li
858*9c5db199SXin Li    def download_cr50_eraseflashinfo_image(self):
859*9c5db199SXin Li        """download the cr50 image that allows erasing flashinfo.
860*9c5db199SXin Li
861*9c5db199SXin Li        Get the file with the matching devid.
862*9c5db199SXin Li
863*9c5db199SXin Li        @return: A tuple with the debug image local path and version
864*9c5db199SXin Li        """
865*9c5db199SXin Li        devid = self._devid.replace(' ', '-').replace('0x', '')
866*9c5db199SXin Li        gsurl = os.path.join(self.GS_PRIVATE_DBG,
867*9c5db199SXin Li                             self.CR50_ERASEFLASHINFO_FILE % devid)
868*9c5db199SXin Li        return self.download_cr50_gs_image(gsurl, None, None)
869*9c5db199SXin Li
870*9c5db199SXin Li    def download_cr50_debug_image(self, devid='', image_bid=''):
871*9c5db199SXin Li        """download the cr50 debug file.
872*9c5db199SXin Li
873*9c5db199SXin Li        Get the file with the matching devid and image board id info
874*9c5db199SXin Li
875*9c5db199SXin Li        @param image_bid: the image board id info string or list
876*9c5db199SXin Li        @return: A tuple with the debug image local path and version
877*9c5db199SXin Li        """
878*9c5db199SXin Li        bid_ext = ''
879*9c5db199SXin Li        # Add the image bid string to the filename
880*9c5db199SXin Li        if image_bid:
881*9c5db199SXin Li            image_bid = cr50_utils.GetBoardIdInfoString(
882*9c5db199SXin Li                    image_bid, symbolic=True)
883*9c5db199SXin Li            bid_ext = '.' + image_bid.replace(':', '_')
884*9c5db199SXin Li
885*9c5db199SXin Li        devid = devid if devid else self._devid
886*9c5db199SXin Li        dbg_file = self.CR50_DEBUG_FILE % (devid.replace(' ', '_'), bid_ext)
887*9c5db199SXin Li        gsurl = os.path.join(self.GS_PRIVATE_DBG, dbg_file)
888*9c5db199SXin Li        return self.download_cr50_gs_image(gsurl, None, image_bid)
889*9c5db199SXin Li
890*9c5db199SXin Li    def download_cr50_tot_image(self):
891*9c5db199SXin Li        """download the cr50 TOT image.
892*9c5db199SXin Li
893*9c5db199SXin Li        @return: the local path to the TOT image.
894*9c5db199SXin Li        """
895*9c5db199SXin Li        # TODO(mruthven): use logic from provision_Cr50TOT
896*9c5db199SXin Li        raise error.TestNAError('Could not download TOT image')
897*9c5db199SXin Li
898*9c5db199SXin Li    def _find_release_image_gsurl(self, fn):
899*9c5db199SXin Li        """Find the gs url for the release image"""
900*9c5db199SXin Li        for gsbucket in [self.GS_PUBLIC, self.GS_PRIVATE_PROD]:
901*9c5db199SXin Li            gsurl = os.path.join(gsbucket, fn)
902*9c5db199SXin Li            if self.find_cr50_gs_image(gsurl):
903*9c5db199SXin Li                return gsurl
904*9c5db199SXin Li        raise error.TestFail('%s is not on google storage' % fn)
905*9c5db199SXin Li
906*9c5db199SXin Li    def download_cr50_release_image(self, image_rw, image_bid=''):
907*9c5db199SXin Li        """download the cr50 release file.
908*9c5db199SXin Li
909*9c5db199SXin Li        Get the file with the matching version and image board id info
910*9c5db199SXin Li
911*9c5db199SXin Li        @param image_rw: the rw version string
912*9c5db199SXin Li        @param image_bid: the image board id info string or list
913*9c5db199SXin Li        @return: A tuple with the release image local path and version
914*9c5db199SXin Li        """
915*9c5db199SXin Li        bid_ext = ''
916*9c5db199SXin Li        # Add the image bid string to the gsurl
917*9c5db199SXin Li        if image_bid:
918*9c5db199SXin Li            image_bid = cr50_utils.GetBoardIdInfoString(
919*9c5db199SXin Li                    image_bid, symbolic=True)
920*9c5db199SXin Li            bid_ext = '_' + image_bid.replace(':', '_')
921*9c5db199SXin Li        release_fn = self.CR50_PROD_FILE % (image_rw, bid_ext)
922*9c5db199SXin Li        gsurl = self._find_release_image_gsurl(release_fn)
923*9c5db199SXin Li
924*9c5db199SXin Li        # Release images can be found using the rw version
925*9c5db199SXin Li        # Download the image
926*9c5db199SXin Li        dest, ver = self.download_cr50_gs_image(gsurl, 'cr50.bin.prod',
927*9c5db199SXin Li                                                image_bid)
928*9c5db199SXin Li
929*9c5db199SXin Li        # Compare the rw version and board id info to make sure the right image
930*9c5db199SXin Li        # was found
931*9c5db199SXin Li        if image_rw != ver[1]:
932*9c5db199SXin Li            raise error.TestError('Could not download image with matching '
933*9c5db199SXin Li                                  'rw version')
934*9c5db199SXin Li        return dest, ver
935*9c5db199SXin Li
936*9c5db199SXin Li    def _cr50_verify_update(self, expected_rw, expect_rollback):
937*9c5db199SXin Li        """Verify the expected version is running on cr50.
938*9c5db199SXin Li
939*9c5db199SXin Li        @param expected_rw: The RW version string we expect to be running
940*9c5db199SXin Li        @param expect_rollback: True if cr50 should have rolled back during the
941*9c5db199SXin Li                                update
942*9c5db199SXin Li        @raise TestFail: if there is any unexpected update state
943*9c5db199SXin Li        """
944*9c5db199SXin Li        errors = []
945*9c5db199SXin Li        running_rw = self.cr50.get_version()
946*9c5db199SXin Li        if expected_rw != running_rw:
947*9c5db199SXin Li            errors.append('running %s not %s' % (running_rw, expected_rw))
948*9c5db199SXin Li
949*9c5db199SXin Li        if expect_rollback != self.cr50.rolledback():
950*9c5db199SXin Li            errors.append('%srollback detected' %
951*9c5db199SXin Li                          'no ' if expect_rollback else '')
952*9c5db199SXin Li        if len(errors):
953*9c5db199SXin Li            raise error.TestFail('cr50_update failed: %s' % ', '.join(errors))
954*9c5db199SXin Li        logging.info('RUNNING %s after %s', expected_rw,
955*9c5db199SXin Li                     'rollback' if expect_rollback else 'update')
956*9c5db199SXin Li
957*9c5db199SXin Li    def _cr50_run_update(self, path):
958*9c5db199SXin Li        """Install the image at path onto cr50.
959*9c5db199SXin Li
960*9c5db199SXin Li        @param path: the location of the image to update to
961*9c5db199SXin Li        @return: the rw version of the image
962*9c5db199SXin Li        """
963*9c5db199SXin Li        tmp_dest = '/tmp/' + os.path.basename(path)
964*9c5db199SXin Li
965*9c5db199SXin Li        # Make sure the dut is sshable before installing the image.
966*9c5db199SXin Li        self._try_to_bring_dut_up()
967*9c5db199SXin Li
968*9c5db199SXin Li        dest, image_ver = cr50_utils.InstallImage(self.host, path, tmp_dest)
969*9c5db199SXin Li        # Use the -p option to make sure the DUT does a clean reboot.
970*9c5db199SXin Li        cr50_utils.GSCTool(self.host, ['-a', dest, '-p'])
971*9c5db199SXin Li        # Reboot the DUT to finish the cr50 update.
972*9c5db199SXin Li        self.host.reboot(wait=False)
973*9c5db199SXin Li        return image_ver[1]
974*9c5db199SXin Li
975*9c5db199SXin Li    def cr50_update(self, path, rollback=False, expect_rollback=False):
976*9c5db199SXin Li        """Attempt to update to the given image.
977*9c5db199SXin Li
978*9c5db199SXin Li        If rollback is True, we assume that cr50 is already running an image
979*9c5db199SXin Li        that can rollback.
980*9c5db199SXin Li
981*9c5db199SXin Li        @param path: the location of the update image
982*9c5db199SXin Li        @param rollback: True if we need to force cr50 to rollback to update to
983*9c5db199SXin Li                         the given image
984*9c5db199SXin Li        @param expect_rollback: True if cr50 should rollback on its own
985*9c5db199SXin Li        @raise TestFail: if the update failed
986*9c5db199SXin Li        """
987*9c5db199SXin Li        original_rw = self.cr50.get_version()
988*9c5db199SXin Li
989*9c5db199SXin Li        # Cr50 is going to reject an update if it hasn't been up for more than
990*9c5db199SXin Li        # 60 seconds. Wait until that passes before trying to run the update.
991*9c5db199SXin Li        self.cr50.wait_until_update_is_allowed()
992*9c5db199SXin Li
993*9c5db199SXin Li        image_rw = self._cr50_run_update(path)
994*9c5db199SXin Li
995*9c5db199SXin Li        # Running the update may cause cr50 to reboot. Wait for that before
996*9c5db199SXin Li        # sending more commands. The reboot should happen quickly.
997*9c5db199SXin Li        self.cr50.wait_for_reboot(
998*9c5db199SXin Li                timeout=self.faft_config.gsc_update_wait_for_reboot)
999*9c5db199SXin Li
1000*9c5db199SXin Li        if rollback:
1001*9c5db199SXin Li            self.cr50.rollback()
1002*9c5db199SXin Li
1003*9c5db199SXin Li        expected_rw = original_rw if expect_rollback else image_rw
1004*9c5db199SXin Li        # If we expect a rollback, the version should remain unchanged
1005*9c5db199SXin Li        self._cr50_verify_update(expected_rw, rollback or expect_rollback)
1006*9c5db199SXin Li
1007*9c5db199SXin Li    def run_gsctool_cmd_with_password(self, password, cmd, name, expect_error):
1008*9c5db199SXin Li        """Run a gsctool command and input the password
1009*9c5db199SXin Li
1010*9c5db199SXin Li        @param password: The cr50 password string
1011*9c5db199SXin Li        @param cmd: The gsctool command
1012*9c5db199SXin Li        @param name: The name to give the job
1013*9c5db199SXin Li        @param expect_error: True if the command should fail
1014*9c5db199SXin Li        """
1015*9c5db199SXin Li        logging.info('Running: %s', cmd)
1016*9c5db199SXin Li        logging.info('Password: %s', password)
1017*9c5db199SXin Li        # Make sure the test waits long enough to avoid ccd rate limiting.
1018*9c5db199SXin Li        time.sleep(self.cr50.CCD_PASSWORD_RATE_LIMIT)
1019*9c5db199SXin Li        full_cmd = "echo -e '%s\n%s\n' | %s" % (password, password, cmd)
1020*9c5db199SXin Li        result = self.host.run(full_cmd, ignore_status=expect_error)
1021*9c5db199SXin Li        if result.exit_status:
1022*9c5db199SXin Li            message = ('gsctool %s failed using %r: %s %s' %
1023*9c5db199SXin Li                       (name, password, result.exit_status, result.stderr))
1024*9c5db199SXin Li            if expect_error:
1025*9c5db199SXin Li                logging.info(message)
1026*9c5db199SXin Li            else:
1027*9c5db199SXin Li                raise error.TestFail(message)
1028*9c5db199SXin Li        elif expect_error:
1029*9c5db199SXin Li            raise error.TestFail('%s with %r did not fail when expected' %
1030*9c5db199SXin Li                                 (name, password))
1031*9c5db199SXin Li        else:
1032*9c5db199SXin Li            logging.info('ran %s password command: %r', name, result.stdout)
1033*9c5db199SXin Li
1034*9c5db199SXin Li    def set_ccd_password(self, password, expect_error=False):
1035*9c5db199SXin Li        """Set the ccd password"""
1036*9c5db199SXin Li        # Testlab mode can't be enabled if there is no power button, so we
1037*9c5db199SXin Li        # shouldn't allow setting the password.
1038*9c5db199SXin Li        if not self.faft_config.has_powerbutton:
1039*9c5db199SXin Li            raise error.TestError('No power button')
1040*9c5db199SXin Li
1041*9c5db199SXin Li        # If for some reason the test sets a password and is interrupted before
1042*9c5db199SXin Li        # we can clear it, we want testlab mode to be enabled, so it's possible
1043*9c5db199SXin Li        # to clear the password without knowing it.
1044*9c5db199SXin Li        if not self.cr50.testlab_is_on():
1045*9c5db199SXin Li            raise error.TestError('Will not set password unless testlab mode '
1046*9c5db199SXin Li                                  'is enabled.')
1047*9c5db199SXin Li        try:
1048*9c5db199SXin Li            self.run_gsctool_cmd_with_password(password, 'gsctool -a -P',
1049*9c5db199SXin Li                                               'set_password', expect_error)
1050*9c5db199SXin Li        finally:
1051*9c5db199SXin Li            logging.info('Cr50 password is %s',
1052*9c5db199SXin Li                         'cleared' if self.cr50.password_is_reset() else 'set')
1053*9c5db199SXin Li
1054*9c5db199SXin Li    def ccd_unlock_from_ap(self, password=None, expect_error=False):
1055*9c5db199SXin Li        """Unlock cr50"""
1056*9c5db199SXin Li        if not password:
1057*9c5db199SXin Li            self.host.run('gsctool -a -U')
1058*9c5db199SXin Li            return
1059*9c5db199SXin Li        self.run_gsctool_cmd_with_password(password, 'gsctool -a -U', 'unlock',
1060*9c5db199SXin Li                                           expect_error)
1061*9c5db199SXin Li
1062*9c5db199SXin Li    def tpm_is_responsive(self):
1063*9c5db199SXin Li        """Check TPM responsiveness by running tpm_version."""
1064*9c5db199SXin Li        result = self.host.run('tpm_version', ignore_status=True)
1065*9c5db199SXin Li        logging.debug(result.stdout.strip())
1066*9c5db199SXin Li        return not result.exit_status
1067