1# Copyright 2020 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
6"""The autotest performing FW update, both EC and AP in CCD mode."""
7import logging
8
9from autotest_lib.client.common_lib import error
10from autotest_lib.server.cros.faft.cr50_test import Cr50Test
11from autotest_lib.server.cros.servo import servo
12
13MAX_TRIES=2
14
15
16class firmware_Cr50CCDFirmwareUpdate(Cr50Test):
17    """A test that can provision a machine to the correct firmware version."""
18
19    version = 1
20    should_restore_fw = False
21
22    def initialize(self, host, cmdline_args, full_args, fw_path=None):
23        """Initialize the test and check if cr50 exists.
24
25        Raises:
26            TestNAError: If the dut is not proper for this test for its RDD
27                         recognition problem.
28        """
29        super(firmware_Cr50CCDFirmwareUpdate,
30              self).initialize(host, cmdline_args, full_args)
31
32        # Don't bother if there is no Chrome EC.
33        if not self.check_ec_capability():
34            raise error.TestNAError('Nothing needs to be tested on this device')
35
36        self.fw_path = fw_path
37        self.b_ver = ''
38        servo_type = self.servo.get_servo_version()
39        if 'ccd' not in servo_type:
40            raise error.TestNAError('unsupported servo type: %s' % servo_type)
41
42        if eval(full_args.get('backup_fw', 'False')):
43            self.backup_firmware()
44
45    def cleanup(self):
46        try:
47            if not self.should_restore_fw:
48                return
49
50            self.cr50.reboot()
51            self.switcher.mode_aware_reboot(reboot_type='cold')
52
53            # Verify the EC is responsive before raising an error and going to
54            # cleanup. Repair and cleanup don't recover corrupted EC firmware
55            # very well.
56            try:
57                self.verify_ec_response()
58            except Exception as e:
59                logging.error('Caught exception: %s', str(e))
60
61            if self.is_firmware_saved():
62                logging.info('Restoring firmware')
63                self.restore_firmware()
64            else:
65                logging.info('chromeos-firmwareupdate --mode=recovery')
66                result = self._client.run('chromeos-firmwareupdate'
67                                          ' --mode=recovery',
68                                          ignore_status=True)
69                if result.exit_status != 0:
70                    logging.error('chromeos-firmwareupdate failed: %s',
71                                  result.stdout.strip())
72                self._client.reboot()
73        except Exception as e:
74            logging.error('Caught exception: %s', str(e))
75        finally:
76            super(firmware_Cr50CCDFirmwareUpdate, self).cleanup()
77
78    def verify_ec_response(self):
79        """ Verify the EC is responsive."""
80        # Try to reflash EC a couple of times to see if it's possible to recover
81        # the device now.
82        count = MAX_TRIES
83        while True:
84            try:
85                if self.servo.get_ec_board():
86                    return
87            except servo.ConsoleError as e:
88                logging.error('EC console is unresponsive: %s', str(e))
89
90            if count == 0:
91                break
92
93            count -= 1
94            # In the last iteration, try with main servo device.
95            if count == 0:
96                self.servo.enable_main_servo_device()
97
98            try:
99                self.cros_host.firmware_install(build=self.b_ver,
100                                                local_tarball=self.fw_path,
101                                                install_bios=False)
102            except Exception as e:
103                logging.error('firmware_install failed: %s', str(e))
104
105        logging.error('DUT likely needs a manual recovery.')
106
107    def run_once(self, host, rw_only=False):
108        """The method called by the control file to start the test.
109
110        Args:
111          host: a CrosHost object of the machine to update.
112          rw_only: True to only update the RW firmware.
113
114        Raises:
115          TestFail: if the firmware version remains unchanged.
116          TestError: if the latest firmware release cannot be located.
117          TestNAError: if the test environment is not properly set.
118                       e.g. the servo type doesn't support this test.
119        """
120        self.cros_host = host
121        # Get the parent (a.k.a. reference board or baseboard), and hand it
122        # to get_latest_release_version so that it
123        # can use it in search as secondary candidate. For example, bob doesn't
124        # have its own release directory, but its parent, gru does.
125        parent = getattr(self.faft_config, 'parent', None)
126
127        if not self.fw_path:
128            self.b_ver = host.get_latest_release_version(
129                    self.faft_config.platform, parent)
130            if not self.b_ver:
131                raise error.TestError(
132                        'Cannot locate the latest release for %s' %
133                        self.faft_config.platform)
134
135        # Fast open cr50 and check if testlab is enabled.
136        self.fast_ccd_open(enable_testlab=True)
137        if not self.servo.enable_ccd_servo_device():
138            raise error.TestNAError('Cannot make ccd active')
139        # TODO(b/196824029): remove when servod supports using the power state
140        # controller with the ccd device.
141        try:
142            self.host.servo.get_power_state_controller().reset()
143        except Exception as e:
144            logging.info(e)
145            raise error.TestNAError('Unable to do power state reset with '
146                                    'active ccd device')
147
148        # If it is ITE EC, then ccd reset factory.
149        if self.servo.get('ec_chip') == 'it83xx':
150            self.cr50.set_cap('I2C', 'Always')
151
152        self.should_restore_fw = True
153        try:
154            self.cros_host.firmware_install(build=self.b_ver,
155                                            rw_only=rw_only,
156                                            local_tarball=self.fw_path,
157                                            dest=self.resultsdir,
158                                            verify_version=True)
159        except Exception as e:
160            # The test failed to flash the firmware.
161            raise error.TestFail('firmware_install failed with CCD: %s' %
162                                 str(e))
163