xref: /aosp_15_r20/external/autotest/server/cros/faft/firmware_test.py (revision 9c5db1993ded3edbeafc8092d69fe5de2ee02df7)
1*9c5db199SXin Li# Lint as: python2, python3
2*9c5db199SXin Li# Copyright (c) 2014 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 ctypes
9*9c5db199SXin Liimport logging
10*9c5db199SXin Liimport os
11*9c5db199SXin Liimport pprint
12*9c5db199SXin Liimport re
13*9c5db199SXin Liimport time
14*9c5db199SXin Liimport uuid
15*9c5db199SXin Lifrom xml.parsers import expat
16*9c5db199SXin Li
17*9c5db199SXin Liimport six
18*9c5db199SXin Lifrom autotest_lib.client.bin import utils
19*9c5db199SXin Lifrom autotest_lib.client.common_lib import error, global_config
20*9c5db199SXin Lifrom autotest_lib.client.common_lib.cros import dev_server, retry, tpm_utils
21*9c5db199SXin Lifrom autotest_lib.server import test
22*9c5db199SXin Lifrom autotest_lib.server.cros import vboot_constants as vboot
23*9c5db199SXin Lifrom autotest_lib.server.cros.faft import telemetry
24*9c5db199SXin Lifrom autotest_lib.server.cros.faft.rpc_proxy import RPCProxy
25*9c5db199SXin Lifrom autotest_lib.server.cros.faft.utils import (menu_mode_switcher,
26*9c5db199SXin Li                                                 menu_navigator, mode_switcher)
27*9c5db199SXin Lifrom autotest_lib.server.cros.faft.utils.config import Config as FAFTConfig
28*9c5db199SXin Lifrom autotest_lib.server.cros.faft.utils.faft_checkers import FAFTCheckers
29*9c5db199SXin Lifrom autotest_lib.server.cros.power import utils as PowerUtils
30*9c5db199SXin Lifrom autotest_lib.server.cros.servo import (chrome_base_ec, chrome_cr50,
31*9c5db199SXin Li                                            chrome_ec, chrome_ti50, servo)
32*9c5db199SXin Lifrom autotest_lib.site_utils import test_runner_utils
33*9c5db199SXin Li
34*9c5db199SXin Li# Experimentally tuned time in minutes to wait for partition device nodes on a
35*9c5db199SXin Li# USB stick to be ready after plugging in the stick.
36*9c5db199SXin LiPARTITION_TABLE_READINESS_TIMEOUT = 0.1  # minutes
37*9c5db199SXin Li# Experimentally tuned time in seconds to wait for the first retry of reading
38*9c5db199SXin Li# the sysfs node of a USB stick's partition device node.
39*9c5db199SXin LiPARTITION_TABLE_READINESS_FIRST_RETRY_DELAY = 1  # seconds
40*9c5db199SXin Li
41*9c5db199SXin LiConnectionError = mode_switcher.ConnectionError
42*9c5db199SXin Li
43*9c5db199SXin Li
44*9c5db199SXin Liclass FirmwareTest(test.test):
45*9c5db199SXin Li    """
46*9c5db199SXin Li    Base class that sets up helper objects/functions for firmware tests.
47*9c5db199SXin Li
48*9c5db199SXin Li    It launches the FAFTClient on DUT, such that the test can access its
49*9c5db199SXin Li    firmware functions and interfaces. It also provides some methods to
50*9c5db199SXin Li    handle the reboot mechanism, in order to ensure FAFTClient is still
51*9c5db199SXin Li    connected after reboot.
52*9c5db199SXin Li    @type servo: servo.Servo
53*9c5db199SXin Li    @type _client: autotest_lib.server.hosts.ssh_host.SSHHost |
54*9c5db199SXin Li                   autotest_lib.server.hosts.cros_host.CrosHost
55*9c5db199SXin Li
56*9c5db199SXin Li    TODO: add documentaion as the FAFT rework progresses.
57*9c5db199SXin Li    """
58*9c5db199SXin Li    version = 1
59*9c5db199SXin Li
60*9c5db199SXin Li    # Set this to True in test classes that need to boot from the USB stick.
61*9c5db199SXin Li    # When True, initialize() will raise TestWarn if USB stick is marked bad.
62*9c5db199SXin Li    NEEDS_SERVO_USB = False
63*9c5db199SXin Li
64*9c5db199SXin Li    # Mapping of partition number of kernel and rootfs.
65*9c5db199SXin Li    KERNEL_MAP = {'a':'2', 'b':'4', '2':'2', '4':'4', '3':'2', '5':'4'}
66*9c5db199SXin Li    ROOTFS_MAP = {'a':'3', 'b':'5', '2':'3', '4':'5', '3':'3', '5':'5'}
67*9c5db199SXin Li    OTHER_KERNEL_MAP = {'a':'4', 'b':'2', '2':'4', '4':'2', '3':'4', '5':'2'}
68*9c5db199SXin Li    OTHER_ROOTFS_MAP = {'a':'5', 'b':'3', '2':'5', '4':'3', '3':'5', '5':'3'}
69*9c5db199SXin Li
70*9c5db199SXin Li    # Mapping of kernel type and name.
71*9c5db199SXin Li    KERNEL_TYPE_NAME_MAP = {'KERN': 'kernel', 'MINIOS': 'minios'}
72*9c5db199SXin Li
73*9c5db199SXin Li    CHROMEOS_MAGIC = "CHROMEOS"
74*9c5db199SXin Li    CORRUPTED_MAGIC = "CORRUPTD"
75*9c5db199SXin Li
76*9c5db199SXin Li    # System power states
77*9c5db199SXin Li    POWER_STATE_S0 = 'S0'
78*9c5db199SXin Li    POWER_STATE_S0IX = 'S0ix'
79*9c5db199SXin Li    POWER_STATE_S3 = 'S3'
80*9c5db199SXin Li    POWER_STATE_S5 = 'S5'
81*9c5db199SXin Li    POWER_STATE_G3 = 'G3'
82*9c5db199SXin Li    POWER_STATE_SUSPEND = '|'.join([POWER_STATE_S0IX, POWER_STATE_S3])
83*9c5db199SXin Li
84*9c5db199SXin Li    # Delay for waiting client to return before EC suspend
85*9c5db199SXin Li    EC_SUSPEND_DELAY = 5
86*9c5db199SXin Li
87*9c5db199SXin Li    # Delay between EC suspend and wake
88*9c5db199SXin Li    WAKE_DELAY = 10
89*9c5db199SXin Li
90*9c5db199SXin Li    # Delay between closing and opening lid
91*9c5db199SXin Li    LID_DELAY = 1
92*9c5db199SXin Li
93*9c5db199SXin Li    # Delay for establishing state after changing PD settings
94*9c5db199SXin Li    PD_RESYNC_DELAY = 2
95*9c5db199SXin Li
96*9c5db199SXin Li    # Delay to wait for servo USB to work after power role swap:
97*9c5db199SXin Li    # tPSSourceOff (920ms) + tPSSourceOn (480ms) + buffer
98*9c5db199SXin Li    POWER_ROLE_SWAP_DELAY = 2
99*9c5db199SXin Li
100*9c5db199SXin Li    # The default number of power state check retries (each try takes 3 secs)
101*9c5db199SXin Li    DEFAULT_PWR_RETRIES = 5
102*9c5db199SXin Li
103*9c5db199SXin Li    # FWMP space constants
104*9c5db199SXin Li    FWMP_CLEARED_EXIT_STATUS = 1
105*9c5db199SXin Li    FWMP_CLEARED_ERROR_MSG = ('CRYPTOHOME_ERROR_FIRMWARE_MANAGEMENT_PARAMETERS'
106*9c5db199SXin Li                              '_INVALID')
107*9c5db199SXin Li
108*9c5db199SXin Li    _ROOTFS_PARTITION_NUMBER = 3
109*9c5db199SXin Li
110*9c5db199SXin Li    # Class level variable, keep track the states of one time setup.
111*9c5db199SXin Li    # This variable is preserved across tests which inherit this class.
112*9c5db199SXin Li    _global_setup_done = {
113*9c5db199SXin Li        'gbb_flags': False,
114*9c5db199SXin Li        'reimage': False,
115*9c5db199SXin Li        'usb_check': False,
116*9c5db199SXin Li    }
117*9c5db199SXin Li
118*9c5db199SXin Li    # CCD password used by tests.
119*9c5db199SXin Li    CCD_PASSWORD = 'Password'
120*9c5db199SXin Li
121*9c5db199SXin Li    RESPONSE_TIMEOUT = 180
122*9c5db199SXin Li
123*9c5db199SXin Li    @classmethod
124*9c5db199SXin Li    def check_setup_done(cls, label):
125*9c5db199SXin Li        """Check if the given setup is done.
126*9c5db199SXin Li
127*9c5db199SXin Li        @param label: The label of the setup.
128*9c5db199SXin Li        """
129*9c5db199SXin Li        return cls._global_setup_done[label]
130*9c5db199SXin Li
131*9c5db199SXin Li    @classmethod
132*9c5db199SXin Li    def mark_setup_done(cls, label):
133*9c5db199SXin Li        """Mark the given setup done.
134*9c5db199SXin Li
135*9c5db199SXin Li        @param label: The label of the setup.
136*9c5db199SXin Li        """
137*9c5db199SXin Li        cls._global_setup_done[label] = True
138*9c5db199SXin Li
139*9c5db199SXin Li    @classmethod
140*9c5db199SXin Li    def unmark_setup_done(cls, label):
141*9c5db199SXin Li        """Mark the given setup not done.
142*9c5db199SXin Li
143*9c5db199SXin Li        @param label: The label of the setup.
144*9c5db199SXin Li        """
145*9c5db199SXin Li        cls._global_setup_done[label] = False
146*9c5db199SXin Li
147*9c5db199SXin Li    def initialize(self, host, cmdline_args, ec_wp=None):
148*9c5db199SXin Li        """Initialize the FirmwareTest.
149*9c5db199SXin Li
150*9c5db199SXin Li        This method interacts with the Servo, FAFT RPC client, FAFT Config,
151*9c5db199SXin Li        Mode Switcher, EC consoles, write-protection, GBB flags, and a lockfile.
152*9c5db199SXin Li
153*9c5db199SXin Li        @type host: autotest_lib.server.hosts.CrosHost
154*9c5db199SXin Li        """
155*9c5db199SXin Li        self.run_id = str(uuid.uuid4())
156*9c5db199SXin Li        self._client = host
157*9c5db199SXin Li        self.servo = host.servo
158*9c5db199SXin Li        if self.servo is None:
159*9c5db199SXin Li            raise error.TestError('FirmwareTest failed to set up servo')
160*9c5db199SXin Li
161*9c5db199SXin Li        self.lockfile = '/usr/local/tmp/faft/lock'
162*9c5db199SXin Li        self._backup_gbb_flags = None
163*9c5db199SXin Li        self._backup_firmware_identity = dict()
164*9c5db199SXin Li        self._backup_kernel_sha = dict()
165*9c5db199SXin Li        for kernel_type in self.KERNEL_TYPE_NAME_MAP:
166*9c5db199SXin Li            self._backup_kernel_sha[kernel_type] = dict()
167*9c5db199SXin Li        self._backup_cgpt_attr = dict()
168*9c5db199SXin Li        self._backup_dev_mode = None
169*9c5db199SXin Li        self._restore_power_mode = None
170*9c5db199SXin Li        self._uart_file_dict = {}
171*9c5db199SXin Li
172*9c5db199SXin Li        logging.info('FirmwareTest initialize begin (id=%s)', self.run_id)
173*9c5db199SXin Li
174*9c5db199SXin Li        # Parse arguments from command line
175*9c5db199SXin Li        args = {}
176*9c5db199SXin Li        self.power_control = host.POWER_CONTROL_RPM
177*9c5db199SXin Li        for arg in cmdline_args:
178*9c5db199SXin Li            match = re.search("^(\w+)=(.+)", arg)
179*9c5db199SXin Li            if match:
180*9c5db199SXin Li                args[match.group(1)] = match.group(2)
181*9c5db199SXin Li
182*9c5db199SXin Li        self._no_fw_rollback_check = False
183*9c5db199SXin Li        if 'no_fw_rollback_check' in args:
184*9c5db199SXin Li            if 'true' in args['no_fw_rollback_check'].lower():
185*9c5db199SXin Li                self._no_fw_rollback_check = True
186*9c5db199SXin Li
187*9c5db199SXin Li        self._no_ec_sync = False
188*9c5db199SXin Li        if 'no_ec_sync' in args:
189*9c5db199SXin Li            if 'true' in args['no_ec_sync'].lower():
190*9c5db199SXin Li                self._no_ec_sync = True
191*9c5db199SXin Li
192*9c5db199SXin Li        self._use_sync_script = global_config.global_config.get_config_value(
193*9c5db199SXin Li                'CROS', 'enable_fs_sync_script', type=bool, default=False)
194*9c5db199SXin Li
195*9c5db199SXin Li        self.servo.initialize_dut()
196*9c5db199SXin Li        self.faft_client = RPCProxy(host)
197*9c5db199SXin Li        self.faft_config = FAFTConfig(
198*9c5db199SXin Li                self.faft_client.system.get_platform_name(),
199*9c5db199SXin Li                self.faft_client.system.get_model_name())
200*9c5db199SXin Li        self.checkers = FAFTCheckers(self)
201*9c5db199SXin Li
202*9c5db199SXin Li        # Mapping of kernel type and kernel servicer class
203*9c5db199SXin Li        self.kernel_servicer = {
204*9c5db199SXin Li                'KERN': self.faft_client.kernel,
205*9c5db199SXin Li                'MINIOS': self.faft_client.minios,
206*9c5db199SXin Li        }
207*9c5db199SXin Li
208*9c5db199SXin Li        if self.faft_config.chrome_ec:
209*9c5db199SXin Li            self.ec = chrome_ec.ChromeEC(self.servo)
210*9c5db199SXin Li        self.menu_navigator = menu_navigator.create_menu_navigator(self)
211*9c5db199SXin Li        self.switcher = mode_switcher.create_mode_switcher(
212*9c5db199SXin Li                self, self.menu_navigator)
213*9c5db199SXin Li        # This will be None for menuless UI
214*9c5db199SXin Li        self.menu_switcher = menu_mode_switcher.create_menu_mode_switcher(
215*9c5db199SXin Li                self, self.menu_navigator)
216*9c5db199SXin Li        # Check for presence of a USBPD console
217*9c5db199SXin Li        if self.faft_config.chrome_usbpd:
218*9c5db199SXin Li            self.usbpd = chrome_ec.ChromeUSBPD(self.servo)
219*9c5db199SXin Li        elif self.faft_config.chrome_ec:
220*9c5db199SXin Li            # If no separate USBPD console, then PD exists on EC console
221*9c5db199SXin Li            self.usbpd = self.ec
222*9c5db199SXin Li        # Get pdtester console
223*9c5db199SXin Li        self.pdtester = host.pdtester
224*9c5db199SXin Li        self.pdtester_host = host._pdtester_host
225*9c5db199SXin Li        gsc = None
226*9c5db199SXin Li        if self.servo.has_control('ti50_version') or \
227*9c5db199SXin Li            self.servo.has_control('ti50_version', 'ccd_gsc'):
228*9c5db199SXin Li            gsc = chrome_ti50.ChromeTi50(self.servo, self.faft_config)
229*9c5db199SXin Li        elif self.servo.has_control('cr50_version'):
230*9c5db199SXin Li            gsc = chrome_cr50.ChromeCr50(self.servo, self.faft_config)
231*9c5db199SXin Li        if gsc:
232*9c5db199SXin Li            try:
233*9c5db199SXin Li                # Check that the gsc console works before declaring the
234*9c5db199SXin Li                # connection exists and enabling uart capture.
235*9c5db199SXin Li                gsc.get_version()
236*9c5db199SXin Li                self.cr50 = gsc
237*9c5db199SXin Li            except servo.ControlUnavailableError:
238*9c5db199SXin Li                logging.warning('gsc console not supported.')
239*9c5db199SXin Li            except Exception as e:
240*9c5db199SXin Li                logging.warning('Ignored unknown gsc version error: %s',
241*9c5db199SXin Li                                str(e))
242*9c5db199SXin Li
243*9c5db199SXin Li        if 'power_control' in args:
244*9c5db199SXin Li            self.power_control = args['power_control']
245*9c5db199SXin Li            if self.power_control not in host.POWER_CONTROL_VALID_ARGS:
246*9c5db199SXin Li                raise error.TestError('Valid values for --args=power_control '
247*9c5db199SXin Li                                      'are %s. But you entered wrong argument '
248*9c5db199SXin Li                                      'as "%s".'
249*9c5db199SXin Li                                      % (host.POWER_CONTROL_VALID_ARGS,
250*9c5db199SXin Li                                         self.power_control))
251*9c5db199SXin Li
252*9c5db199SXin Li        if self.NEEDS_SERVO_USB and not host.is_servo_usb_usable():
253*9c5db199SXin Li            usb_state = host.get_servo_usb_state()
254*9c5db199SXin Li            raise error.TestWarn(
255*9c5db199SXin Li                    "Servo USB disk unusable (%s); canceling test." %
256*9c5db199SXin Li                    usb_state)
257*9c5db199SXin Li
258*9c5db199SXin Li        if not self.faft_client.system.dev_tpm_present():
259*9c5db199SXin Li            raise error.TestError('/dev/tpm0 does not exist on the client')
260*9c5db199SXin Li
261*9c5db199SXin Li        # Initialize servo role to src
262*9c5db199SXin Li        self.servo.set_servo_v4_role('src')
263*9c5db199SXin Li
264*9c5db199SXin Li        # Create the BaseEC object. None if not available.
265*9c5db199SXin Li        self.base_ec = chrome_base_ec.create_base_ec(self.servo)
266*9c5db199SXin Li
267*9c5db199SXin Li        self._record_uart_capture()
268*9c5db199SXin Li        self._record_system_info()
269*9c5db199SXin Li        self.faft_client.system.set_dev_default_boot()
270*9c5db199SXin Li        self.fw_vboot2 = self.faft_client.system.get_fw_vboot2()
271*9c5db199SXin Li        logging.info('vboot version: %d', 2 if self.fw_vboot2 else 1)
272*9c5db199SXin Li        if self.fw_vboot2:
273*9c5db199SXin Li            self.faft_client.system.set_fw_try_next('A')
274*9c5db199SXin Li            if self.faft_client.system.get_crossystem_value(
275*9c5db199SXin Li                    'mainfw_act') == 'B':
276*9c5db199SXin Li                logging.info('mainfw_act is B. rebooting to set it A')
277*9c5db199SXin Li                # TODO(crbug.com/1018322): remove try/catch once that bug is
278*9c5db199SXin Li                # marked as fixed and verified. In that case the overlay for
279*9c5db199SXin Li                # the board itself will map warm_reset to cold_reset.
280*9c5db199SXin Li                try:
281*9c5db199SXin Li                    self.switcher.mode_aware_reboot()
282*9c5db199SXin Li                except ConnectionError as e:
283*9c5db199SXin Li                    if 'DUT is still up unexpectedly' in str(e):
284*9c5db199SXin Li                        # In this case, try doing a cold_reset instead
285*9c5db199SXin Li                        self.switcher.mode_aware_reboot(reboot_type='cold')
286*9c5db199SXin Li                    else:
287*9c5db199SXin Li                        raise
288*9c5db199SXin Li
289*9c5db199SXin Li        # Check flashrom before first use, to avoid xmlrpclib.Fault.
290*9c5db199SXin Li        if not self.faft_client.bios.is_available():
291*9c5db199SXin Li            raise error.TestError(
292*9c5db199SXin Li                    "flashrom is broken; check 'flashrom -p host'"
293*9c5db199SXin Li                    "and rpc server log.")
294*9c5db199SXin Li
295*9c5db199SXin Li        self._setup_gbb_flags()
296*9c5db199SXin Li        self.faft_client.updater.stop_daemon()
297*9c5db199SXin Li        self._create_faft_lockfile()
298*9c5db199SXin Li        self._setup_ec_write_protect(ec_wp)
299*9c5db199SXin Li        # See chromium:239034 regarding needing this sync.
300*9c5db199SXin Li        self.blocking_sync()
301*9c5db199SXin Li        logging.info('FirmwareTest initialize done (id=%s)', self.run_id)
302*9c5db199SXin Li
303*9c5db199SXin Li    def stage_build_to_usbkey(self):
304*9c5db199SXin Li        """Downloads host's build to the USB key attached to servo.
305*9c5db199SXin Li
306*9c5db199SXin Li        @return: True if build is verified to be on USB key, False otherwise.
307*9c5db199SXin Li        """
308*9c5db199SXin Li        info = self._client.host_info_store.get()
309*9c5db199SXin Li        if info.build and info.build != test_runner_utils.NO_BUILD:
310*9c5db199SXin Li            current_build = self._client._servo_host.validate_image_usbkey()
311*9c5db199SXin Li            if current_build != info.build:
312*9c5db199SXin Li                logging.debug('Current build on USB: %s differs from test'
313*9c5db199SXin Li                              ' build: %s, proceed with download.',
314*9c5db199SXin Li                              current_build, info.build)
315*9c5db199SXin Li                try:
316*9c5db199SXin Li                    self._client.stage_build_to_usb(info.build)
317*9c5db199SXin Li                    return True
318*9c5db199SXin Li                except (error.AutotestError,
319*9c5db199SXin Li                        dev_server.DevServerException) as e:
320*9c5db199SXin Li                    logging.warning(
321*9c5db199SXin Li                            'Stage build to USB failed, tests that require'
322*9c5db199SXin Li                            ' test image on Servo USB may fail: {}'.format(e))
323*9c5db199SXin Li                    return False
324*9c5db199SXin Li            else:
325*9c5db199SXin Li                logging.debug('Current build on USB: %s is same as test'
326*9c5db199SXin Li                              ' build, skip download.', current_build)
327*9c5db199SXin Li                return True
328*9c5db199SXin Li        else:
329*9c5db199SXin Li            logging.warning('Failed to get build label from the DUT, will use'
330*9c5db199SXin Li                            ' existing image in Servo USB.')
331*9c5db199SXin Li            return False
332*9c5db199SXin Li
333*9c5db199SXin Li    def run_once(self, *args, **dargs):
334*9c5db199SXin Li        """Delegates testing to a test method.
335*9c5db199SXin Li
336*9c5db199SXin Li        test_name is either the 1st positional argument or a named argument.
337*9c5db199SXin Li
338*9c5db199SXin Li        test_name will be mapped to a test method as follows:
339*9c5db199SXin Li        test_name                     method
340*9c5db199SXin Li        --------------                -----------
341*9c5db199SXin Li        <TestClass>                   test
342*9c5db199SXin Li        <TestClass>.<Case>            test_<Case>
343*9c5db199SXin Li        <TestClass>.<Case>.<SubCase>  test_<Case>_<SubCase>
344*9c5db199SXin Li
345*9c5db199SXin Li        Any arguments not consumed by FirmwareTest are passed to the test method.
346*9c5db199SXin Li
347*9c5db199SXin Li        @param test_name: Should be set to NAME in the control file.
348*9c5db199SXin Li
349*9c5db199SXin Li        @raise TestError: If test_name wasn't found in args, does not start
350*9c5db199SXin Li                          with test class, or if the method is not found.
351*9c5db199SXin Li        """
352*9c5db199SXin Li        self_name = type(self).__name__
353*9c5db199SXin Li
354*9c5db199SXin Li        # Parse and remove test name from args.
355*9c5db199SXin Li        if 'test_name' in dargs:
356*9c5db199SXin Li            test_name = dargs.pop('test_name')
357*9c5db199SXin Li        elif len(args) >= 1:
358*9c5db199SXin Li            test_name = args[0]
359*9c5db199SXin Li            args = args[1:]
360*9c5db199SXin Li        else:
361*9c5db199SXin Li            raise error.TestError('"%s" class must define run_once, or the'
362*9c5db199SXin Li                                  ' control file must specify "test_name".' %
363*9c5db199SXin Li                                  self_name)
364*9c5db199SXin Li
365*9c5db199SXin Li        # Check that test_name starts with the test class name.
366*9c5db199SXin Li        name_parts = test_name.split('.')
367*9c5db199SXin Li
368*9c5db199SXin Li        test_class = name_parts.pop(0)
369*9c5db199SXin Li        if test_class != self_name:
370*9c5db199SXin Li            raise error.TestError('Class "%s" does not match that found in test'
371*9c5db199SXin Li                                  ' name "%s"' % (self_name, test_class))
372*9c5db199SXin Li
373*9c5db199SXin Li        # Construct and call the test method.
374*9c5db199SXin Li        method_name = '_'.join(['test'] + name_parts)
375*9c5db199SXin Li        if not hasattr(self, method_name):
376*9c5db199SXin Li            raise error.TestError('Method "%s" for testing "%s" not found in'
377*9c5db199SXin Li                                  ' "%s"' % (method_name, test_name, self_name))
378*9c5db199SXin Li
379*9c5db199SXin Li        logging.info('Starting test: "%s"', test_name)
380*9c5db199SXin Li        utils.cherry_pick_call(getattr(self, method_name), *args, **dargs)
381*9c5db199SXin Li
382*9c5db199SXin Li    def cleanup(self):
383*9c5db199SXin Li        """Autotest cleanup function."""
384*9c5db199SXin Li        # Unset state checker in case it's set by subclass
385*9c5db199SXin Li        logging.info('FirmwareTest cleaning up (id=%s)', self.run_id)
386*9c5db199SXin Li
387*9c5db199SXin Li        # Capture UART before doing anything else, so we can guarantee we get
388*9c5db199SXin Li        # some uart results.
389*9c5db199SXin Li        try:
390*9c5db199SXin Li            self._record_uart_capture()
391*9c5db199SXin Li        except:
392*9c5db199SXin Li            logging.warning('Failed initial uart capture during cleanup')
393*9c5db199SXin Li
394*9c5db199SXin Li        try:
395*9c5db199SXin Li            self.faft_client.system.is_available()
396*9c5db199SXin Li        except:
397*9c5db199SXin Li            # Remote is not responding. Revive DUT so that subsequent tests
398*9c5db199SXin Li            # don't fail.
399*9c5db199SXin Li            self._restore_routine_from_timeout()
400*9c5db199SXin Li
401*9c5db199SXin Li        if hasattr(self, 'switcher'):
402*9c5db199SXin Li            self.switcher.restore_mode()
403*9c5db199SXin Li
404*9c5db199SXin Li        self._restore_ec_write_protect()
405*9c5db199SXin Li        self._restore_servo_v4_role()
406*9c5db199SXin Li
407*9c5db199SXin Li        if hasattr(self, 'faft_client'):
408*9c5db199SXin Li            self._restore_gbb_flags()
409*9c5db199SXin Li            self.faft_client.updater.start_daemon()
410*9c5db199SXin Li            self.faft_client.updater.cleanup()
411*9c5db199SXin Li            self._remove_faft_lockfile()
412*9c5db199SXin Li            self.faft_client.quit()
413*9c5db199SXin Li            self.faft_client.collect_logfiles(self.resultsdir)
414*9c5db199SXin Li
415*9c5db199SXin Li        # Capture any new uart output, then discard log messages again.
416*9c5db199SXin Li        self._cleanup_uart_capture()
417*9c5db199SXin Li
418*9c5db199SXin Li        super(FirmwareTest, self).cleanup()
419*9c5db199SXin Li        logging.info('FirmwareTest cleanup done (id=%s)', self.run_id)
420*9c5db199SXin Li
421*9c5db199SXin Li    def _record_system_info(self):
422*9c5db199SXin Li        """Record some critical system info to the attr keyval.
423*9c5db199SXin Li
424*9c5db199SXin Li        This info is used by generate_test_report later.
425*9c5db199SXin Li        """
426*9c5db199SXin Li        system_info = {
427*9c5db199SXin Li            'hwid': self.faft_client.system.get_crossystem_value('hwid'),
428*9c5db199SXin Li            'ec_version': self.faft_client.ec.get_version(),
429*9c5db199SXin Li            'ro_fwid': self.faft_client.system.get_crossystem_value('ro_fwid'),
430*9c5db199SXin Li            'rw_fwid': self.faft_client.system.get_crossystem_value('fwid'),
431*9c5db199SXin Li            'servo_host_os_version' : self.servo.get_os_version(),
432*9c5db199SXin Li            'servod_version': self.servo.get_servod_version(),
433*9c5db199SXin Li            'os_version': self._client.get_release_builder_path(),
434*9c5db199SXin Li            'servo_type': self.servo.get_servo_version()
435*9c5db199SXin Li        }
436*9c5db199SXin Li
437*9c5db199SXin Li        # Record the servo v4 and servo micro versions when possible
438*9c5db199SXin Li        system_info.update(self.servo.get_servo_fw_versions())
439*9c5db199SXin Li
440*9c5db199SXin Li        if hasattr(self, 'cr50'):
441*9c5db199SXin Li            system_info['cr50_version'] = self.cr50.get_full_version()
442*9c5db199SXin Li
443*9c5db199SXin Li        logging.info('System info:\n%s', pprint.pformat(system_info))
444*9c5db199SXin Li        self.write_attr_keyval(system_info)
445*9c5db199SXin Li
446*9c5db199SXin Li    def invalidate_firmware_setup(self):
447*9c5db199SXin Li        """Invalidate all firmware related setup state.
448*9c5db199SXin Li
449*9c5db199SXin Li        This method is called when the firmware is re-flashed. It resets all
450*9c5db199SXin Li        firmware related setup states so that the next test setup properly
451*9c5db199SXin Li        again.
452*9c5db199SXin Li        """
453*9c5db199SXin Li        self.unmark_setup_done('gbb_flags')
454*9c5db199SXin Li
455*9c5db199SXin Li    def _retrieve_recovery_reason_from_trap(self):
456*9c5db199SXin Li        """Try to retrieve the recovery reason from a trapped recovery screen.
457*9c5db199SXin Li
458*9c5db199SXin Li        @return: The recovery_reason, 0 if any error.
459*9c5db199SXin Li        """
460*9c5db199SXin Li        recovery_reason = 0
461*9c5db199SXin Li        logging.info('Try to retrieve recovery reason...')
462*9c5db199SXin Li        if self.servo.get_usbkey_state() == 'dut':
463*9c5db199SXin Li            self.switcher.bypass_rec_mode()
464*9c5db199SXin Li        else:
465*9c5db199SXin Li            self.servo.switch_usbkey('dut')
466*9c5db199SXin Li
467*9c5db199SXin Li        try:
468*9c5db199SXin Li            self.switcher.wait_for_client()
469*9c5db199SXin Li            lines = self.faft_client.system.run_shell_command_get_output(
470*9c5db199SXin Li                        'crossystem recovery_reason')
471*9c5db199SXin Li            recovery_reason = int(lines[0])
472*9c5db199SXin Li            logging.info('Got the recovery reason %d.', recovery_reason)
473*9c5db199SXin Li        except ConnectionError:
474*9c5db199SXin Li            logging.error('Failed to get the recovery reason due to connection '
475*9c5db199SXin Li                          'error.')
476*9c5db199SXin Li        return recovery_reason
477*9c5db199SXin Li
478*9c5db199SXin Li    def _reset_client(self):
479*9c5db199SXin Li        """Reset client to a workable state.
480*9c5db199SXin Li
481*9c5db199SXin Li        This method is called when the client is not responsive. It may be
482*9c5db199SXin Li        caused by the following cases:
483*9c5db199SXin Li          - halt on a firmware screen without timeout, e.g. REC_INSERT screen;
484*9c5db199SXin Li          - corrupted firmware;
485*9c5db199SXin Li          - corrutped OS image.
486*9c5db199SXin Li        """
487*9c5db199SXin Li        # DUT may halt on a firmware screen. Try cold reboot.
488*9c5db199SXin Li        logging.info('Try cold reboot...')
489*9c5db199SXin Li        self.switcher.mode_aware_reboot(reboot_type='cold',
490*9c5db199SXin Li                                        sync_before_boot=False,
491*9c5db199SXin Li                                        wait_for_dut_up=False)
492*9c5db199SXin Li        self.switcher.wait_for_client_offline()
493*9c5db199SXin Li        self.switcher.bypass_dev_mode()
494*9c5db199SXin Li        try:
495*9c5db199SXin Li            self.switcher.wait_for_client()
496*9c5db199SXin Li            return
497*9c5db199SXin Li        except ConnectionError:
498*9c5db199SXin Li            logging.warning("Cold reboot didn't help, still connection error.")
499*9c5db199SXin Li
500*9c5db199SXin Li        # DUT may be broken by a corrupted firmware. Restore firmware.
501*9c5db199SXin Li        # We assume the recovery boot still works fine. Since the recovery
502*9c5db199SXin Li        # code is in RO region and all FAFT tests don't change the RO region
503*9c5db199SXin Li        # except GBB.
504*9c5db199SXin Li        if self.is_firmware_saved():
505*9c5db199SXin Li            self._ensure_client_in_recovery()
506*9c5db199SXin Li            logging.info('Try restoring the original firmware...')
507*9c5db199SXin Li            try:
508*9c5db199SXin Li                if self.restore_firmware():
509*9c5db199SXin Li                    return
510*9c5db199SXin Li            except ConnectionError:
511*9c5db199SXin Li                logging.warning("Restoring firmware didn't help, still "
512*9c5db199SXin Li                                "connection error.")
513*9c5db199SXin Li
514*9c5db199SXin Li        # Perhaps it's kernel that's broken. Let's try restoring it.
515*9c5db199SXin Li        for kernel_type, kernel_name in self.KERNEL_TYPE_NAME_MAP.items():
516*9c5db199SXin Li            if self.is_kernel_saved(kernel_type):
517*9c5db199SXin Li                self._ensure_client_in_recovery()
518*9c5db199SXin Li                logging.info('Try restoring the original %s...', kernel_name)
519*9c5db199SXin Li                try:
520*9c5db199SXin Li                    if self.restore_kernel(kernel_type=kernel_type):
521*9c5db199SXin Li                        return
522*9c5db199SXin Li                except ConnectionError:
523*9c5db199SXin Li                    logging.warning(
524*9c5db199SXin Li                            "Restoring %s didn't help, still "
525*9c5db199SXin Li                            "connection error.", kernel_name)
526*9c5db199SXin Li
527*9c5db199SXin Li        # DUT may be broken by a corrupted OS image. Restore OS image.
528*9c5db199SXin Li        self._ensure_client_in_recovery()
529*9c5db199SXin Li        logging.info('Try restoring the OS image...')
530*9c5db199SXin Li        self.faft_client.system.run_shell_command('chromeos-install --yes')
531*9c5db199SXin Li        self.switcher.mode_aware_reboot(wait_for_dut_up=False)
532*9c5db199SXin Li        self.switcher.wait_for_client_offline()
533*9c5db199SXin Li        self.switcher.bypass_dev_mode()
534*9c5db199SXin Li        try:
535*9c5db199SXin Li            self.switcher.wait_for_client()
536*9c5db199SXin Li            logging.info('Successfully restored OS image.')
537*9c5db199SXin Li            return
538*9c5db199SXin Li        except ConnectionError:
539*9c5db199SXin Li            logging.warning("Restoring OS image didn't help, still connection "
540*9c5db199SXin Li                            "error.")
541*9c5db199SXin Li
542*9c5db199SXin Li    def _ensure_client_in_recovery(self):
543*9c5db199SXin Li        """Ensure client in recovery boot; reboot into it if necessary.
544*9c5db199SXin Li
545*9c5db199SXin Li        @raise TestError: if failed to boot the USB image.
546*9c5db199SXin Li        """
547*9c5db199SXin Li        logging.info('Try booting into USB image...')
548*9c5db199SXin Li        self.switcher.reboot_to_mode(to_mode='rec', sync_before_boot=False,
549*9c5db199SXin Li                                     wait_for_dut_up=False)
550*9c5db199SXin Li        self.servo.switch_usbkey('host')
551*9c5db199SXin Li        self.switcher.bypass_rec_mode()
552*9c5db199SXin Li        try:
553*9c5db199SXin Li            self.switcher.wait_for_client()
554*9c5db199SXin Li        except ConnectionError:
555*9c5db199SXin Li            raise error.TestError('Failed to boot the USB image.')
556*9c5db199SXin Li
557*9c5db199SXin Li    def _restore_routine_from_timeout(self):
558*9c5db199SXin Li        """A routine to try to restore the system from a timeout error.
559*9c5db199SXin Li
560*9c5db199SXin Li        This method is called when FAFT failed to connect DUT after reboot.
561*9c5db199SXin Li
562*9c5db199SXin Li        @raise TestFail: This exception is already raised, with a decription
563*9c5db199SXin Li                         why it failed.
564*9c5db199SXin Li        """
565*9c5db199SXin Li        # DUT is disconnected. Capture the UART output for debug.
566*9c5db199SXin Li        self._record_uart_capture()
567*9c5db199SXin Li
568*9c5db199SXin Li        # Replug the Ethernet to identify if it is a network flaky.
569*9c5db199SXin Li        self.servo.eth_power_reset()
570*9c5db199SXin Li
571*9c5db199SXin Li        recovery_reason = self._retrieve_recovery_reason_from_trap()
572*9c5db199SXin Li
573*9c5db199SXin Li        # Reset client to a workable state.
574*9c5db199SXin Li        self._reset_client()
575*9c5db199SXin Li
576*9c5db199SXin Li        # Raise the proper TestFail exception.
577*9c5db199SXin Li        if recovery_reason:
578*9c5db199SXin Li            raise error.TestFail('Trapped in the recovery screen (reason: %d) '
579*9c5db199SXin Li                                 'and timed out' % recovery_reason)
580*9c5db199SXin Li        else:
581*9c5db199SXin Li            raise error.TestFail('Timed out waiting for DUT reboot')
582*9c5db199SXin Li
583*9c5db199SXin Li    def assert_test_image_in_usb_disk(self, usb_dev=None):
584*9c5db199SXin Li        """Assert an USB disk plugged-in on servo and a test image inside.
585*9c5db199SXin Li
586*9c5db199SXin Li        @param usb_dev: A string of USB stick path on the host, like '/dev/sdc'.
587*9c5db199SXin Li                        If None, it is detected automatically.
588*9c5db199SXin Li        @raise TestError: if USB disk not detected or not a test image.
589*9c5db199SXin Li        """
590*9c5db199SXin Li        if self.check_setup_done('usb_check'):
591*9c5db199SXin Li            return
592*9c5db199SXin Li        if usb_dev:
593*9c5db199SXin Li            assert self.servo.get_usbkey_state() == 'host'
594*9c5db199SXin Li        else:
595*9c5db199SXin Li            self.servo.switch_usbkey('host')
596*9c5db199SXin Li            usb_dev = self.servo.probe_host_usb_dev()
597*9c5db199SXin Li            if not usb_dev:
598*9c5db199SXin Li                raise error.TestError(
599*9c5db199SXin Li                    'An USB disk should be plugged in the servo board. %s' %
600*9c5db199SXin Li                    telemetry.collect_usb_state(self.servo))
601*9c5db199SXin Li
602*9c5db199SXin Li        rootfs = '%s%s' % (usb_dev, self._ROOTFS_PARTITION_NUMBER)
603*9c5db199SXin Li        logging.info('usb dev is %s', usb_dev)
604*9c5db199SXin Li        tmpd = self.servo.system_output('mktemp -d -t usbcheck.XXXX')
605*9c5db199SXin Li        # After the USB key is muxed from the DUT to the servo host, there
606*9c5db199SXin Li        # appears to be a delay between when servod can confirm that a sysfs
607*9c5db199SXin Li        # entry exists for the disk (as done by probe_host_usb_dev) and when
608*9c5db199SXin Li        # sysfs entries get populated for the disk's partitions.
609*9c5db199SXin Li        @retry.retry(error.AutoservRunError,
610*9c5db199SXin Li                     timeout_min=PARTITION_TABLE_READINESS_TIMEOUT,
611*9c5db199SXin Li                     delay_sec=PARTITION_TABLE_READINESS_FIRST_RETRY_DELAY)
612*9c5db199SXin Li        def confirm_rootfs_partition_device_node_readable():
613*9c5db199SXin Li            """Repeatedly poll for the RootFS partition sysfs node."""
614*9c5db199SXin Li            self.servo.system('ls {}'.format(rootfs))
615*9c5db199SXin Li
616*9c5db199SXin Li        try:
617*9c5db199SXin Li            confirm_rootfs_partition_device_node_readable()
618*9c5db199SXin Li        except error.AutoservRunError as e:
619*9c5db199SXin Li            usb_info = telemetry.collect_usb_state(self.servo)
620*9c5db199SXin Li            raise error.TestError(
621*9c5db199SXin Li                    ('Could not ls the device node for the RootFS on the USB '
622*9c5db199SXin Li                     'device. %s: %s\nMore telemetry: %s') %
623*9c5db199SXin Li                    (type(e).__name__, e, usb_info))
624*9c5db199SXin Li        try:
625*9c5db199SXin Li            self.servo.system('mount -o ro %s %s' % (rootfs, tmpd))
626*9c5db199SXin Li        except error.AutoservRunError as e:
627*9c5db199SXin Li            usb_info = telemetry.collect_usb_state(self.servo)
628*9c5db199SXin Li            raise error.TestError(
629*9c5db199SXin Li                ('Could not mount the partition on USB device. %s: %s\n'
630*9c5db199SXin Li                 'More telemetry: %s') % (type(e).__name__, e, usb_info))
631*9c5db199SXin Li
632*9c5db199SXin Li        try:
633*9c5db199SXin Li            usb_lsb = self.servo.system_output('cat %s' %
634*9c5db199SXin Li                os.path.join(tmpd, 'etc/lsb-release'))
635*9c5db199SXin Li            logging.debug('Dumping lsb-release on USB stick:\n%s', usb_lsb)
636*9c5db199SXin Li            dut_lsb = '\n'.join(self.faft_client.system.
637*9c5db199SXin Li                run_shell_command_get_output('cat /etc/lsb-release'))
638*9c5db199SXin Li            logging.debug('Dumping lsb-release on DUT:\n%s', dut_lsb)
639*9c5db199SXin Li            if not re.search(r'RELEASE_TRACK=.*test', usb_lsb):
640*9c5db199SXin Li                raise error.TestError('USB stick in servo is no test image')
641*9c5db199SXin Li            usb_board = re.search(r'BOARD=(.*)', usb_lsb).group(1)
642*9c5db199SXin Li            dut_board = re.search(r'BOARD=(.*)', dut_lsb).group(1)
643*9c5db199SXin Li            if usb_board != dut_board:
644*9c5db199SXin Li                raise error.TestError('USB stick in servo contains a %s '
645*9c5db199SXin Li                    'image, but DUT is a %s' % (usb_board, dut_board))
646*9c5db199SXin Li        finally:
647*9c5db199SXin Li            for cmd in ('umount -l %s' % tmpd, 'sync %s' % usb_dev,
648*9c5db199SXin Li                        'rm -rf %s' % tmpd):
649*9c5db199SXin Li                self.servo.system(cmd)
650*9c5db199SXin Li
651*9c5db199SXin Li        self.mark_setup_done('usb_check')
652*9c5db199SXin Li
653*9c5db199SXin Li    def setup_pdtester(self, flip_cc=False, dts_mode=False, pd_faft=True,
654*9c5db199SXin Li                       min_batt_level=None):
655*9c5db199SXin Li        """Setup the PDTester to a given state.
656*9c5db199SXin Li
657*9c5db199SXin Li        @param flip_cc: True to flip CC polarity; False to not flip it.
658*9c5db199SXin Li        @param dts_mode: True to config PDTester to DTS mode; False to not.
659*9c5db199SXin Li        @param pd_faft: True to config PD FAFT setup.
660*9c5db199SXin Li        @param min_batt_level: An int for minimum battery level, or None for
661*9c5db199SXin Li                               skip.
662*9c5db199SXin Li        @raise TestError: If Servo v4 not setup properly.
663*9c5db199SXin Li        """
664*9c5db199SXin Li
665*9c5db199SXin Li        # PD FAFT is only tested with a combination of servo_v4 or servo_v4p1
666*9c5db199SXin Li        # with servo micro or C2D2.
667*9c5db199SXin Li        pd_setup = []
668*9c5db199SXin Li        for first in self.pdtester.FIRST_PD_SETUP_ELEMENT:
669*9c5db199SXin Li            for second in self.pdtester.SECOND_PD_SETUP_ELEMENT:
670*9c5db199SXin Li                pd_setup.append(first + '_with_' + second)
671*9c5db199SXin Li
672*9c5db199SXin Li        if pd_faft and self.pdtester.servo_type not in pd_setup:
673*9c5db199SXin Li            raise error.TestError(', '.join(pd_setup) +
674*9c5db199SXin Li                                  ' is a mandatory setup '
675*9c5db199SXin Li                                  'for PD FAFT. Got %s.' %
676*9c5db199SXin Li                                  self.pdtester.servo_type)
677*9c5db199SXin Li
678*9c5db199SXin Li        # Ensure the battery is enough for testing, this should be done before
679*9c5db199SXin Li        # all the following setup.
680*9c5db199SXin Li        if (min_batt_level is not None) and self._client.has_battery():
681*9c5db199SXin Li            logging.info('Start charging if batt level < %d', min_batt_level)
682*9c5db199SXin Li            PowerUtils.put_host_battery_in_range(self._client, min_batt_level,
683*9c5db199SXin Li                                                 100, 600)
684*9c5db199SXin Li
685*9c5db199SXin Li        # Servo v4 by default has dts_mode enabled. Enabling dts_mode affects
686*9c5db199SXin Li        # the behaviors of what PD FAFT tests. So we want it disabled.
687*9c5db199SXin Li        pd_tester_device = self.pdtester.servo_type.split('_with_')[0]
688*9c5db199SXin Li        if pd_tester_device in self.pdtester.FIRST_PD_SETUP_ELEMENT:
689*9c5db199SXin Li            self.servo.set_dts_mode('on' if dts_mode else 'off')
690*9c5db199SXin Li        else:
691*9c5db199SXin Li            logging.warning('Configuring DTS mode only supported on %s',
692*9c5db199SXin Li                            pd_tester_device)
693*9c5db199SXin Li
694*9c5db199SXin Li        self.pdtester.set('usbc_polarity', 'cc2' if flip_cc else 'cc1')
695*9c5db199SXin Li        # Make it sourcing max voltage.
696*9c5db199SXin Li        self.pdtester.charge(self.pdtester.USBC_MAX_VOLTAGE)
697*9c5db199SXin Li
698*9c5db199SXin Li        time.sleep(self.PD_RESYNC_DELAY)
699*9c5db199SXin Li
700*9c5db199SXin Li        # Servo v4 requires an external charger to source power. Make sure
701*9c5db199SXin Li        # this setup is correct.
702*9c5db199SXin Li        if pd_tester_device in self.pdtester.FIRST_PD_SETUP_ELEMENT:
703*9c5db199SXin Li            role = self.pdtester.get('servo_pd_role')
704*9c5db199SXin Li            if role != 'src':
705*9c5db199SXin Li                raise error.TestError(
706*9c5db199SXin Li                        '%s is not sourcing power! Make sure the servo '
707*9c5db199SXin Li                        '"DUT POWER" port is connected to a working charger. '
708*9c5db199SXin Li                        'servo_pd_role:%s' % (pd_tester_device, role))
709*9c5db199SXin Li
710*9c5db199SXin Li    def setup_usbkey(self, usbkey, host=None, used_for_recovery=None):
711*9c5db199SXin Li        """Setup the USB disk for the test.
712*9c5db199SXin Li
713*9c5db199SXin Li        It checks the setup of USB disk and a valid ChromeOS test image inside.
714*9c5db199SXin Li        It also muxes the USB disk to either the host or DUT by request.
715*9c5db199SXin Li
716*9c5db199SXin Li        @param usbkey: True if the USB disk is required for the test, False if
717*9c5db199SXin Li                       not required.
718*9c5db199SXin Li        @param host: Optional, True to mux the USB disk to host, False to mux it
719*9c5db199SXin Li                    to DUT, default to do nothing.
720*9c5db199SXin Li        @param used_for_recovery: Optional, True if the USB disk is used for
721*9c5db199SXin Li                                  recovery boot; False if the USB disk is not
722*9c5db199SXin Li                                  used for recovery boot, like Ctrl-U USB boot.
723*9c5db199SXin Li        """
724*9c5db199SXin Li        if usbkey:
725*9c5db199SXin Li            self.stage_build_to_usbkey()
726*9c5db199SXin Li            self.assert_test_image_in_usb_disk()
727*9c5db199SXin Li        elif host is None:
728*9c5db199SXin Li            # USB disk is not required for the test. Better to mux it to host.
729*9c5db199SXin Li            host = True
730*9c5db199SXin Li
731*9c5db199SXin Li        if host is True:
732*9c5db199SXin Li            self.servo.switch_usbkey('host')
733*9c5db199SXin Li        elif host is False:
734*9c5db199SXin Li            self.servo.switch_usbkey('dut')
735*9c5db199SXin Li
736*9c5db199SXin Li        if used_for_recovery is None:
737*9c5db199SXin Li            # Default value is True if usbkey == True.
738*9c5db199SXin Li            # As the common usecase of USB disk is for recovery boot. Tests
739*9c5db199SXin Li            # can define it explicitly if not.
740*9c5db199SXin Li            used_for_recovery = usbkey
741*9c5db199SXin Li
742*9c5db199SXin Li        if used_for_recovery:
743*9c5db199SXin Li            # In recovery boot, the locked EC RO doesn't support PD for most
744*9c5db199SXin Li            # of the CrOS devices. The default servo v4 power role is a SRC.
745*9c5db199SXin Li            # The DUT becomes a SNK. Lack of PD makes CrOS unable to do the
746*9c5db199SXin Li            # data role swap from UFP to DFP; as a result, DUT can't see the
747*9c5db199SXin Li            # USB disk and the Ethernet dongle on servo v4.
748*9c5db199SXin Li            #
749*9c5db199SXin Li            # This is a workaround to set servo v4 as a SNK, for every FAFT
750*9c5db199SXin Li            # test which boots into the USB disk in the recovery mode.
751*9c5db199SXin Li            #
752*9c5db199SXin Li            # TODO(waihong): Add a check to see if the battery level is too
753*9c5db199SXin Li            # low and sleep for a while for charging.
754*9c5db199SXin Li            self.set_servo_v4_role_to_snk()
755*9c5db199SXin Li
756*9c5db199SXin Li            # Force reconnection; otherwise, the next RPC call will timeout
757*9c5db199SXin Li            logging.info('Waiting for reconnection after power role swap...')
758*9c5db199SXin Li            time.sleep(self.POWER_ROLE_SWAP_DELAY)
759*9c5db199SXin Li            self.faft_client.disconnect()
760*9c5db199SXin Li            self.faft_client.connect()
761*9c5db199SXin Li
762*9c5db199SXin Li    def set_servo_v4_role_to_snk(self, pd_comm=False):
763*9c5db199SXin Li        """Set the servo v4 role to SNK.
764*9c5db199SXin Li
765*9c5db199SXin Li        @param pd_comm: a bool. Enable PD communication if True, else otherwise
766*9c5db199SXin Li        """
767*9c5db199SXin Li        self._needed_restore_servo_v4_role = True
768*9c5db199SXin Li        self.servo.set_servo_v4_role('snk')
769*9c5db199SXin Li        if pd_comm:
770*9c5db199SXin Li            self.servo.set_servo_v4_pd_comm('on')
771*9c5db199SXin Li
772*9c5db199SXin Li    def _restore_servo_v4_role(self):
773*9c5db199SXin Li        """Restore the servo v4 role to default SRC."""
774*9c5db199SXin Li        if not hasattr(self, '_needed_restore_servo_v4_role'):
775*9c5db199SXin Li            return
776*9c5db199SXin Li        if self._needed_restore_servo_v4_role:
777*9c5db199SXin Li            self.servo.set_servo_v4_role('src')
778*9c5db199SXin Li
779*9c5db199SXin Li    def set_dut_low_power_idle_delay(self, delay):
780*9c5db199SXin Li        """Set EC low power idle delay
781*9c5db199SXin Li
782*9c5db199SXin Li        @param delay: Delay in seconds
783*9c5db199SXin Li        """
784*9c5db199SXin Li        if not self.ec.has_command('dsleep'):
785*9c5db199SXin Li            logging.info("Can't set low power idle delay.")
786*9c5db199SXin Li            return
787*9c5db199SXin Li        self._previous_ec_low_power_delay = int(
788*9c5db199SXin Li                self.ec.send_command_get_output("dsleep",
789*9c5db199SXin Li                ["timeout:\s+(\d+)\ssec"])[0][1])
790*9c5db199SXin Li        self.ec.send_command("dsleep " + str(delay))
791*9c5db199SXin Li
792*9c5db199SXin Li    def restore_dut_low_power_idle_delay(self):
793*9c5db199SXin Li        """Restore EC low power idle delay"""
794*9c5db199SXin Li        if getattr(self, '_previous_ec_low_power_delay', None):
795*9c5db199SXin Li            self.ec.send_command("dsleep " + str(
796*9c5db199SXin Li                    self._previous_ec_low_power_delay))
797*9c5db199SXin Li
798*9c5db199SXin Li    def get_usbdisk_path_on_dut(self):
799*9c5db199SXin Li        """Get the path of the USB disk device plugged-in the servo on DUT.
800*9c5db199SXin Li
801*9c5db199SXin Li        Returns:
802*9c5db199SXin Li          A string representing USB disk path, like '/dev/sdb', or None if
803*9c5db199SXin Li          no USB disk is found.
804*9c5db199SXin Li        """
805*9c5db199SXin Li        cmd = 'ls -d /dev/s*[a-z]'
806*9c5db199SXin Li        original_value = self.servo.get_usbkey_state()
807*9c5db199SXin Li
808*9c5db199SXin Li        # Make the dut unable to see the USB disk.
809*9c5db199SXin Li        self.servo.switch_usbkey('off')
810*9c5db199SXin Li        time.sleep(self.faft_config.usb_unplug)
811*9c5db199SXin Li        no_usb_set = set(
812*9c5db199SXin Li            self.faft_client.system.run_shell_command_get_output(cmd))
813*9c5db199SXin Li
814*9c5db199SXin Li        # Make the dut able to see the USB disk.
815*9c5db199SXin Li        self.servo.switch_usbkey('dut')
816*9c5db199SXin Li        time.sleep(self.faft_config.usb_plug)
817*9c5db199SXin Li        has_usb_set = set(
818*9c5db199SXin Li            self.faft_client.system.run_shell_command_get_output(cmd))
819*9c5db199SXin Li
820*9c5db199SXin Li        # Back to its original value.
821*9c5db199SXin Li        if original_value != self.servo.get_usbkey_state():
822*9c5db199SXin Li            self.servo.switch_usbkey(original_value)
823*9c5db199SXin Li
824*9c5db199SXin Li        diff_set = has_usb_set - no_usb_set
825*9c5db199SXin Li        if len(diff_set) == 1:
826*9c5db199SXin Li            return diff_set.pop()
827*9c5db199SXin Li        else:
828*9c5db199SXin Li            return None
829*9c5db199SXin Li
830*9c5db199SXin Li    def _create_faft_lockfile(self):
831*9c5db199SXin Li        """Creates the FAFT lockfile."""
832*9c5db199SXin Li        logging.info('Creating FAFT lockfile...')
833*9c5db199SXin Li        command = 'touch %s' % (self.lockfile)
834*9c5db199SXin Li        self.faft_client.system.run_shell_command(command)
835*9c5db199SXin Li
836*9c5db199SXin Li    def _remove_faft_lockfile(self):
837*9c5db199SXin Li        """Removes the FAFT lockfile."""
838*9c5db199SXin Li        logging.info('Removing FAFT lockfile...')
839*9c5db199SXin Li        command = 'rm -f %s' % (self.lockfile)
840*9c5db199SXin Li        self.faft_client.system.run_shell_command(command)
841*9c5db199SXin Li
842*9c5db199SXin Li    def clear_set_gbb_flags(self, clear_mask, set_mask, reboot=True):
843*9c5db199SXin Li        """Clear and set the GBB flags in the current flashrom.
844*9c5db199SXin Li
845*9c5db199SXin Li        @param clear_mask: A mask of flags to be cleared.
846*9c5db199SXin Li        @param set_mask: A mask of flags to be set.
847*9c5db199SXin Li        @param reboot: If true, then this method will reboot the DUT if certain
848*9c5db199SXin Li                       flags are modified.
849*9c5db199SXin Li        """
850*9c5db199SXin Li        gbb_flags = self.faft_client.bios.get_gbb_flags()
851*9c5db199SXin Li        new_flags = gbb_flags & ctypes.c_uint32(~clear_mask).value | set_mask
852*9c5db199SXin Li        if new_flags == gbb_flags:
853*9c5db199SXin Li            logging.info(
854*9c5db199SXin Li                    'Current GBB flags (0x%x) match desired clear mask '
855*9c5db199SXin Li                    '(0x%x) and desired set mask (0x%x)', gbb_flags,
856*9c5db199SXin Li                    clear_mask, set_mask)
857*9c5db199SXin Li            return
858*9c5db199SXin Li
859*9c5db199SXin Li        logging.info('Changing GBB flags from 0x%x to 0x%x.', gbb_flags,
860*9c5db199SXin Li                     new_flags)
861*9c5db199SXin Li        if self._backup_gbb_flags is None:
862*9c5db199SXin Li            self._backup_gbb_flags = gbb_flags
863*9c5db199SXin Li        self.faft_client.bios.set_gbb_flags(new_flags)
864*9c5db199SXin Li
865*9c5db199SXin Li        # If changing FORCE_DEV_SWITCH_ON or DISABLE_EC_SOFTWARE_SYNC flag,
866*9c5db199SXin Li        # reboot to get a clear state
867*9c5db199SXin Li        if reboot and bool((gbb_flags ^ new_flags)
868*9c5db199SXin Li                           & (vboot.GBB_FLAG_FORCE_DEV_SWITCH_ON
869*9c5db199SXin Li                              | vboot.GBB_FLAG_DISABLE_EC_SOFTWARE_SYNC)):
870*9c5db199SXin Li            self.switcher.mode_aware_reboot()
871*9c5db199SXin Li
872*9c5db199SXin Li
873*9c5db199SXin Li    def _check_capability(self, target, required_cap, suppress_warning):
874*9c5db199SXin Li        """Check if current platform has required capabilities for the target.
875*9c5db199SXin Li
876*9c5db199SXin Li        @param required_cap: A list containing required capabilities.
877*9c5db199SXin Li        @param suppress_warning: True to suppress any warning messages.
878*9c5db199SXin Li        @return: True if requirements are met. Otherwise, False.
879*9c5db199SXin Li        """
880*9c5db199SXin Li        if not required_cap:
881*9c5db199SXin Li            return True
882*9c5db199SXin Li
883*9c5db199SXin Li        if target not in ['ec', 'cr50']:
884*9c5db199SXin Li            raise error.TestError('Invalid capability target %r' % target)
885*9c5db199SXin Li
886*9c5db199SXin Li        for cap in required_cap:
887*9c5db199SXin Li            if cap not in getattr(self.faft_config, target + '_capability'):
888*9c5db199SXin Li                if not suppress_warning:
889*9c5db199SXin Li                    logging.warning(
890*9c5db199SXin Li                            'Requires %s capability "%s" to run this '
891*9c5db199SXin Li                            'test.', target, cap)
892*9c5db199SXin Li                return False
893*9c5db199SXin Li
894*9c5db199SXin Li        return True
895*9c5db199SXin Li
896*9c5db199SXin Li
897*9c5db199SXin Li    def check_ec_capability(self, required_cap=None, suppress_warning=False):
898*9c5db199SXin Li        """Check if current platform has required EC capabilities.
899*9c5db199SXin Li
900*9c5db199SXin Li        @param required_cap: A list containing required EC capabilities. Pass in
901*9c5db199SXin Li                             None to only check for presence of Chrome EC.
902*9c5db199SXin Li        @param suppress_warning: True to suppress any warning messages.
903*9c5db199SXin Li        @return: True if requirements are met. Otherwise, False.
904*9c5db199SXin Li        """
905*9c5db199SXin Li        if not self.faft_config.chrome_ec:
906*9c5db199SXin Li            if not suppress_warning:
907*9c5db199SXin Li                logging.warning('Requires Chrome EC to run this test.')
908*9c5db199SXin Li            return False
909*9c5db199SXin Li        return self._check_capability('ec', required_cap, suppress_warning)
910*9c5db199SXin Li
911*9c5db199SXin Li
912*9c5db199SXin Li    def check_cr50_capability(self, required_cap=None, suppress_warning=False):
913*9c5db199SXin Li        """Check if current platform has required Cr50 capabilities.
914*9c5db199SXin Li
915*9c5db199SXin Li        @param required_cap: A list containing required Cr50 capabilities. Pass
916*9c5db199SXin Li                             in None to only check for presence of cr50 uart.
917*9c5db199SXin Li        @param suppress_warning: True to suppress any warning messages.
918*9c5db199SXin Li        @return: True if requirements are met. Otherwise, False.
919*9c5db199SXin Li        """
920*9c5db199SXin Li        if not hasattr(self, 'cr50'):
921*9c5db199SXin Li            if not suppress_warning:
922*9c5db199SXin Li                logging.warning('Requires Chrome Cr50 to run this test.')
923*9c5db199SXin Li            return False
924*9c5db199SXin Li        return self._check_capability('cr50', required_cap, suppress_warning)
925*9c5db199SXin Li
926*9c5db199SXin Li
927*9c5db199SXin Li    def check_root_part_on_non_recovery(self, part):
928*9c5db199SXin Li        """Check the partition number of root device and on normal/dev boot.
929*9c5db199SXin Li
930*9c5db199SXin Li        @param part: A string of partition number, e.g.'3'.
931*9c5db199SXin Li        @return: True if the root device matched and on normal/dev boot;
932*9c5db199SXin Li                 otherwise, False.
933*9c5db199SXin Li        """
934*9c5db199SXin Li        return self.checkers.root_part_checker(part) and \
935*9c5db199SXin Li                self.checkers.crossystem_checker({
936*9c5db199SXin Li                    'mainfw_type': ('normal', 'developer'),
937*9c5db199SXin Li                })
938*9c5db199SXin Li
939*9c5db199SXin Li    def _join_part(self, dev, part):
940*9c5db199SXin Li        """Return a concatenated string of device and partition number.
941*9c5db199SXin Li
942*9c5db199SXin Li        @param dev: A string of device, e.g.'/dev/sda'.
943*9c5db199SXin Li        @param part: A string of partition number, e.g.'3'.
944*9c5db199SXin Li        @return: A concatenated string of device and partition number,
945*9c5db199SXin Li                 e.g.'/dev/sda3'.
946*9c5db199SXin Li
947*9c5db199SXin Li        >>> seq = FirmwareTest()
948*9c5db199SXin Li        >>> seq._join_part('/dev/sda', '3')
949*9c5db199SXin Li        '/dev/sda3'
950*9c5db199SXin Li        >>> seq._join_part('/dev/mmcblk0', '2')
951*9c5db199SXin Li        '/dev/mmcblk0p2'
952*9c5db199SXin Li        """
953*9c5db199SXin Li        if 'mmcblk' in dev:
954*9c5db199SXin Li            return dev + 'p' + part
955*9c5db199SXin Li        elif 'nvme' in dev:
956*9c5db199SXin Li            return dev + 'p' + part
957*9c5db199SXin Li        else:
958*9c5db199SXin Li            return dev + part
959*9c5db199SXin Li
960*9c5db199SXin Li    def copy_kernel_and_rootfs(self, from_part, to_part):
961*9c5db199SXin Li        """Copy kernel and rootfs from from_part to to_part.
962*9c5db199SXin Li
963*9c5db199SXin Li        @param from_part: A string of partition number to be copied from.
964*9c5db199SXin Li        @param to_part: A string of partition number to be copied to.
965*9c5db199SXin Li        """
966*9c5db199SXin Li        root_dev = self.faft_client.system.get_root_dev()
967*9c5db199SXin Li        logging.info('Copying kernel from %s to %s. Please wait...',
968*9c5db199SXin Li                     from_part, to_part)
969*9c5db199SXin Li        self.faft_client.system.run_shell_command('dd if=%s of=%s bs=4M' %
970*9c5db199SXin Li                (self._join_part(root_dev, self.KERNEL_MAP[from_part]),
971*9c5db199SXin Li                 self._join_part(root_dev, self.KERNEL_MAP[to_part])))
972*9c5db199SXin Li        logging.info('Copying rootfs from %s to %s. Please wait...',
973*9c5db199SXin Li                     from_part, to_part)
974*9c5db199SXin Li        self.faft_client.system.run_shell_command('dd if=%s of=%s bs=4M' %
975*9c5db199SXin Li                (self._join_part(root_dev, self.ROOTFS_MAP[from_part]),
976*9c5db199SXin Li                 self._join_part(root_dev, self.ROOTFS_MAP[to_part])))
977*9c5db199SXin Li
978*9c5db199SXin Li    def ensure_kernel_boot(self, part):
979*9c5db199SXin Li        """Ensure the request kernel boot.
980*9c5db199SXin Li
981*9c5db199SXin Li        If not, it duplicates the current kernel to the requested kernel
982*9c5db199SXin Li        and sets the requested higher priority to ensure it boot.
983*9c5db199SXin Li
984*9c5db199SXin Li        @param part: A string of kernel partition number or 'a'/'b'.
985*9c5db199SXin Li        """
986*9c5db199SXin Li        if not self.checkers.root_part_checker(part):
987*9c5db199SXin Li            if self.faft_client.kernel.diff_a_b():
988*9c5db199SXin Li                self.copy_kernel_and_rootfs(
989*9c5db199SXin Li                        from_part=self.OTHER_KERNEL_MAP[part],
990*9c5db199SXin Li                        to_part=part)
991*9c5db199SXin Li            self.reset_and_prioritize_kernel(part)
992*9c5db199SXin Li            self.switcher.mode_aware_reboot()
993*9c5db199SXin Li
994*9c5db199SXin Li    def ensure_dev_internal_boot(self, original_dev_boot_usb):
995*9c5db199SXin Li        """Ensure internal device boot in developer mode.
996*9c5db199SXin Li
997*9c5db199SXin Li        If not internal device boot, it will try to reboot the device and
998*9c5db199SXin Li        bypass dev mode to boot into internal device.
999*9c5db199SXin Li
1000*9c5db199SXin Li        @param original_dev_boot_usb: Original dev_boot_usb value.
1001*9c5db199SXin Li        """
1002*9c5db199SXin Li        self.faft_client.system.set_dev_default_boot()
1003*9c5db199SXin Li        if self.faft_client.system.is_removable_device_boot():
1004*9c5db199SXin Li            logging.info('Reboot into internal disk...')
1005*9c5db199SXin Li            self.faft_client.system.set_dev_boot_usb(original_dev_boot_usb)
1006*9c5db199SXin Li            self.switcher.mode_aware_reboot()
1007*9c5db199SXin Li        self.check_state((self.checkers.dev_boot_usb_checker, False,
1008*9c5db199SXin Li                          'Device not booted from internal disk properly.'))
1009*9c5db199SXin Li
1010*9c5db199SXin Li    def set_hardware_write_protect(self, enable):
1011*9c5db199SXin Li        """Set hardware write protect pin.
1012*9c5db199SXin Li
1013*9c5db199SXin Li        @param enable: True if asserting write protect pin. Otherwise, False.
1014*9c5db199SXin Li        """
1015*9c5db199SXin Li        self.servo.set('fw_wp_state', 'force_on' if enable else 'force_off')
1016*9c5db199SXin Li
1017*9c5db199SXin Li    def set_ap_write_protect_and_reboot(self, enable):
1018*9c5db199SXin Li        """Set AP write protect status and reboot to take effect.
1019*9c5db199SXin Li
1020*9c5db199SXin Li        @param enable: True if asserting write protect. Otherwise, False.
1021*9c5db199SXin Li        """
1022*9c5db199SXin Li        self.set_hardware_write_protect(enable)
1023*9c5db199SXin Li        if hasattr(self, 'ec'):
1024*9c5db199SXin Li            self.sync_and_ec_reboot()
1025*9c5db199SXin Li            self.switcher.wait_for_client()
1026*9c5db199SXin Li
1027*9c5db199SXin Li    def run_chromeos_firmwareupdate(self, mode, append=None, options=(),
1028*9c5db199SXin Li            ignore_status=False):
1029*9c5db199SXin Li        """Use RPC to get the command to run, but do the actual run via ssh.
1030*9c5db199SXin Li
1031*9c5db199SXin Li        Running the command via SSH improves the reliability in cases where the
1032*9c5db199SXin Li        USB network connection gets interrupted.  SSH will still return the
1033*9c5db199SXin Li        output, and won't hang like RPC would.
1034*9c5db199SXin Li        """
1035*9c5db199SXin Li        update_cmd = self.faft_client.updater.get_firmwareupdate_command(
1036*9c5db199SXin Li                mode, append, options)
1037*9c5db199SXin Li        try:
1038*9c5db199SXin Li            result = self._client.run(
1039*9c5db199SXin Li                    update_cmd, timeout=300, ignore_status=ignore_status)
1040*9c5db199SXin Li            if result.exit_status == 255:
1041*9c5db199SXin Li                self.faft_client.disconnect()
1042*9c5db199SXin Li            return result
1043*9c5db199SXin Li        except error.AutoservRunError as e:
1044*9c5db199SXin Li            if e.result_obj.exit_status == 255:
1045*9c5db199SXin Li                self.faft_client.disconnect()
1046*9c5db199SXin Li            if ignore_status:
1047*9c5db199SXin Li                return e.result_obj
1048*9c5db199SXin Li            raise
1049*9c5db199SXin Li
1050*9c5db199SXin Li    def set_ec_write_protect_and_reboot(self, enable):
1051*9c5db199SXin Li        """Set EC write protect status and reboot to take effect.
1052*9c5db199SXin Li
1053*9c5db199SXin Li        The write protect state is only activated if both hardware write
1054*9c5db199SXin Li        protect pin is asserted and software write protect flag is set.
1055*9c5db199SXin Li        This method asserts/deasserts hardware write protect pin first, and
1056*9c5db199SXin Li        set corresponding EC software write protect flag.
1057*9c5db199SXin Li
1058*9c5db199SXin Li        If the device uses non-Chrome EC, set the software write protect via
1059*9c5db199SXin Li        flashrom.
1060*9c5db199SXin Li
1061*9c5db199SXin Li        If the device uses Chrome EC, a reboot is required for write protect
1062*9c5db199SXin Li        to take effect. Since the software write protect flag cannot be unset
1063*9c5db199SXin Li        if hardware write protect pin is asserted, we need to deasserted the
1064*9c5db199SXin Li        pin first if we are deactivating write protect. Similarly, a reboot
1065*9c5db199SXin Li        is required before we can modify the software flag.
1066*9c5db199SXin Li
1067*9c5db199SXin Li        @param enable: True if activating EC write protect. Otherwise, False.
1068*9c5db199SXin Li        """
1069*9c5db199SXin Li        self.set_hardware_write_protect(enable)
1070*9c5db199SXin Li        if self.faft_config.chrome_ec:
1071*9c5db199SXin Li            self.set_chrome_ec_write_protect_and_reboot(enable)
1072*9c5db199SXin Li        else:
1073*9c5db199SXin Li            self.faft_client.ec.set_write_protect(enable)
1074*9c5db199SXin Li            self.switcher.mode_aware_reboot()
1075*9c5db199SXin Li
1076*9c5db199SXin Li    def set_chrome_ec_write_protect_and_reboot(self, enable):
1077*9c5db199SXin Li        """Set Chrome EC write protect status and reboot to take effect.
1078*9c5db199SXin Li
1079*9c5db199SXin Li        @param enable: True if activating EC write protect. Otherwise, False.
1080*9c5db199SXin Li        """
1081*9c5db199SXin Li        if enable:
1082*9c5db199SXin Li            # Set write protect flag and reboot to take effect.
1083*9c5db199SXin Li            self.ec.set_flash_write_protect(enable)
1084*9c5db199SXin Li            self.sync_and_ec_reboot(
1085*9c5db199SXin Li                    flags='hard',
1086*9c5db199SXin Li                    extra_sleep=self.faft_config.ec_boot_to_wp_en)
1087*9c5db199SXin Li        else:
1088*9c5db199SXin Li            # Reboot after deasserting hardware write protect pin to deactivate
1089*9c5db199SXin Li            # write protect. And then remove software write protect flag.
1090*9c5db199SXin Li            # Some ITE ECs can only clear their WP status on a power-on reset,
1091*9c5db199SXin Li            # no software-initiated reset will do.
1092*9c5db199SXin Li            self.sync_and_ec_reboot(flags='cold')
1093*9c5db199SXin Li            self.ec.set_flash_write_protect(enable)
1094*9c5db199SXin Li
1095*9c5db199SXin Li    def _setup_ec_write_protect(self, ec_wp):
1096*9c5db199SXin Li        """Setup for EC write-protection.
1097*9c5db199SXin Li
1098*9c5db199SXin Li        It makes sure the EC in the requested write-protection state. If not, it
1099*9c5db199SXin Li        flips the state. Flipping the write-protection requires DUT reboot.
1100*9c5db199SXin Li
1101*9c5db199SXin Li        @param ec_wp: True to request EC write-protected; False to request EC
1102*9c5db199SXin Li                      not write-protected; None to do nothing.
1103*9c5db199SXin Li        """
1104*9c5db199SXin Li        if ec_wp is None:
1105*9c5db199SXin Li            return
1106*9c5db199SXin Li        self._old_wpsw_cur = self.checkers.crossystem_checker(
1107*9c5db199SXin Li                                    {'wpsw_cur': '1'}, suppress_logging=True)
1108*9c5db199SXin Li        if ec_wp != self._old_wpsw_cur:
1109*9c5db199SXin Li            if not self.faft_config.ap_access_ec_flash:
1110*9c5db199SXin Li                raise error.TestNAError(
1111*9c5db199SXin Li                        "Cannot change EC write-protect for this device")
1112*9c5db199SXin Li
1113*9c5db199SXin Li            logging.info('The test required EC is %swrite-protected. Reboot '
1114*9c5db199SXin Li                         'and flip the state.', '' if ec_wp else 'not ')
1115*9c5db199SXin Li            self.switcher.mode_aware_reboot(
1116*9c5db199SXin Li                    'custom',
1117*9c5db199SXin Li                     lambda:self.set_ec_write_protect_and_reboot(ec_wp))
1118*9c5db199SXin Li        wpsw_cur = '1' if ec_wp else '0'
1119*9c5db199SXin Li        self.check_state((self.checkers.crossystem_checker, {
1120*9c5db199SXin Li                               'wpsw_cur': wpsw_cur}))
1121*9c5db199SXin Li
1122*9c5db199SXin Li    def _restore_ec_write_protect(self):
1123*9c5db199SXin Li        """Restore the original EC write-protection."""
1124*9c5db199SXin Li        if (not hasattr(self, '_old_wpsw_cur')) or (self._old_wpsw_cur is
1125*9c5db199SXin Li                                                    None):
1126*9c5db199SXin Li            return
1127*9c5db199SXin Li        if not self.checkers.crossystem_checker({'wpsw_cur': '1' if
1128*9c5db199SXin Li                       self._old_wpsw_cur else '0'}, suppress_logging=True):
1129*9c5db199SXin Li            logging.info('Restore original EC write protection and reboot.')
1130*9c5db199SXin Li            self.switcher.mode_aware_reboot(
1131*9c5db199SXin Li                    'custom',
1132*9c5db199SXin Li                    lambda:self.set_ec_write_protect_and_reboot(
1133*9c5db199SXin Li                            self._old_wpsw_cur))
1134*9c5db199SXin Li        self.check_state((self.checkers.crossystem_checker, {
1135*9c5db199SXin Li                          'wpsw_cur': '1' if self._old_wpsw_cur else '0'}))
1136*9c5db199SXin Li
1137*9c5db199SXin Li    def _record_uart_capture(self):
1138*9c5db199SXin Li        """Record the CPU/EC/PD UART output stream to files."""
1139*9c5db199SXin Li        self.servo.record_uart_capture(self.resultsdir)
1140*9c5db199SXin Li
1141*9c5db199SXin Li    def _cleanup_uart_capture(self):
1142*9c5db199SXin Li        """Cleanup the CPU/EC/PD UART capture."""
1143*9c5db199SXin Li        self.servo.close(self.resultsdir)
1144*9c5db199SXin Li
1145*9c5db199SXin Li    def set_ap_off_power_mode(self, power_mode):
1146*9c5db199SXin Li        """
1147*9c5db199SXin Li        Set the DUT power mode to suspend (S0ix/S3) or shutdown (G3/S5).
1148*9c5db199SXin Li        The DUT must be in S0 when calling this method.
1149*9c5db199SXin Li
1150*9c5db199SXin Li        @param power_mode: a string for the expected power mode, either
1151*9c5db199SXin Li                           'suspend' or 'shutdown'.
1152*9c5db199SXin Li        """
1153*9c5db199SXin Li        if power_mode == 'suspend':
1154*9c5db199SXin Li            target_power_state = self.POWER_STATE_SUSPEND
1155*9c5db199SXin Li        elif power_mode == 'shutdown':
1156*9c5db199SXin Li            target_power_state = self.POWER_STATE_G3
1157*9c5db199SXin Li        else:
1158*9c5db199SXin Li            raise error.TestError('%s is not a valid ap-off power mode.' %
1159*9c5db199SXin Li                                  power_mode)
1160*9c5db199SXin Li
1161*9c5db199SXin Li        if self.get_power_state() != self.POWER_STATE_S0:
1162*9c5db199SXin Li            raise error.TestError('The DUT is not in S0.')
1163*9c5db199SXin Li
1164*9c5db199SXin Li        self._restore_power_mode = True
1165*9c5db199SXin Li
1166*9c5db199SXin Li        if target_power_state == self.POWER_STATE_G3:
1167*9c5db199SXin Li            self.run_shutdown_cmd()
1168*9c5db199SXin Li            time.sleep(self.faft_config.shutdown)
1169*9c5db199SXin Li        elif target_power_state == self.POWER_STATE_SUSPEND:
1170*9c5db199SXin Li            self.suspend()
1171*9c5db199SXin Li
1172*9c5db199SXin Li        if self.wait_power_state(target_power_state, self.DEFAULT_PWR_RETRIES):
1173*9c5db199SXin Li            logging.info('System entered %s state.', target_power_state)
1174*9c5db199SXin Li        else:
1175*9c5db199SXin Li            self._restore_power_mode = False
1176*9c5db199SXin Li            raise error.TestFail('System fail to enter %s state. '
1177*9c5db199SXin Li                                 'Current state: %s' %
1178*9c5db199SXin Li                                 (target_power_state, self.get_power_state()))
1179*9c5db199SXin Li
1180*9c5db199SXin Li    def restore_ap_on_power_mode(self):
1181*9c5db199SXin Li        """
1182*9c5db199SXin Li        Wake up the DUT to S0. If the DUT was not set to suspend or
1183*9c5db199SXin Li        shutdown mode by set_ap_off_power_mode(), raise an error.
1184*9c5db199SXin Li        """
1185*9c5db199SXin Li        if self.get_power_state() != self.POWER_STATE_S0:
1186*9c5db199SXin Li            logging.info('Wake up the DUT to S0.')
1187*9c5db199SXin Li            self.servo.power_normal_press()
1188*9c5db199SXin Li            # If the DUT is ping-able, it must be in S0.
1189*9c5db199SXin Li            self.switcher.wait_for_client()
1190*9c5db199SXin Li            if self._restore_power_mode != True:
1191*9c5db199SXin Li                raise error.TestFail('The DUT was not set to suspend/shutdown '
1192*9c5db199SXin Li                        'mode by set_ap_off_power_mode().')
1193*9c5db199SXin Li            self._restore_power_mode = False
1194*9c5db199SXin Li
1195*9c5db199SXin Li    def get_power_state(self):
1196*9c5db199SXin Li        """
1197*9c5db199SXin Li        Return the current power state of the AP (via EC 'powerinfo' command)
1198*9c5db199SXin Li
1199*9c5db199SXin Li        @return the name of the power state, or None if a problem occurred
1200*9c5db199SXin Li        """
1201*9c5db199SXin Li        if not hasattr(self, 'ec'):
1202*9c5db199SXin Li            # Don't fail when EC not present or not fully initialized
1203*9c5db199SXin Li            return None
1204*9c5db199SXin Li
1205*9c5db199SXin Li        pattern = r'power state (\w+) = (\w+),'
1206*9c5db199SXin Li
1207*9c5db199SXin Li        try:
1208*9c5db199SXin Li            match = self.ec.send_command_get_output("powerinfo", [pattern],
1209*9c5db199SXin Li                                                    retries=3)
1210*9c5db199SXin Li        except (error.TestFail, expat.ExpatError) as err:
1211*9c5db199SXin Li            logging.warning("powerinfo command encountered an error: %s", err)
1212*9c5db199SXin Li            return None
1213*9c5db199SXin Li        if not match:
1214*9c5db199SXin Li            logging.warning("powerinfo output did not match pattern: %r",
1215*9c5db199SXin Li                            pattern)
1216*9c5db199SXin Li            return None
1217*9c5db199SXin Li        (line, state_num, state_name) = match[0]
1218*9c5db199SXin Li        logging.debug("power state info %r", match)
1219*9c5db199SXin Li        return state_name
1220*9c5db199SXin Li
1221*9c5db199SXin Li    def _check_power_state(self, expected_power_state, actual_power_state):
1222*9c5db199SXin Li        """
1223*9c5db199SXin Li        Check for correct power state of the AP (via EC 'powerinfo' command)
1224*9c5db199SXin Li
1225*9c5db199SXin Li        @param expected_power_state: full-string regex of power state you are
1226*9c5db199SXin Li        expecting
1227*9c5db199SXin Li        @param actual_power_state: the power state returned from get_power_state
1228*9c5db199SXin Li        @return: the line and the match, if the output matched.
1229*9c5db199SXin Li        @raise error.TestFail: if output didn't match after the delay.
1230*9c5db199SXin Li        """
1231*9c5db199SXin Li        if not isinstance(expected_power_state, six.string_types):
1232*9c5db199SXin Li            raise error.TestError('%s is not a string while it should be.' %
1233*9c5db199SXin Li                                  expected_power_state)
1234*9c5db199SXin Li        if not isinstance(actual_power_state, six.string_types):
1235*9c5db199SXin Li            raise error.TestError('%s is not a string while it should be.' %
1236*9c5db199SXin Li                                  actual_power_state)
1237*9c5db199SXin Li        if re.match('^' + expected_power_state + '$', actual_power_state):
1238*9c5db199SXin Li            return True
1239*9c5db199SXin Li        return False
1240*9c5db199SXin Li
1241*9c5db199SXin Li    def wait_power_state(self, power_state, retries, retry_delay=3):
1242*9c5db199SXin Li        """
1243*9c5db199SXin Li        Wait for certain power state.
1244*9c5db199SXin Li
1245*9c5db199SXin Li        @param power_state: full-string regex of power state you are expecting
1246*9c5db199SXin Li        @param retries: retries.  This is necessary if AP is powering down
1247*9c5db199SXin Li        and transitioning through different states.
1248*9c5db199SXin Li        @param retry_delay: delay between retries in seconds
1249*9c5db199SXin Li        """
1250*9c5db199SXin Li        logging.info('Checking power state "%s" maximum %d times.',
1251*9c5db199SXin Li                     power_state, retries)
1252*9c5db199SXin Li
1253*9c5db199SXin Li        last_power_state = ''
1254*9c5db199SXin Li        while retries > 0:
1255*9c5db199SXin Li            logging.debug("try count: %d", retries)
1256*9c5db199SXin Li            start_time = time.time()
1257*9c5db199SXin Li            try:
1258*9c5db199SXin Li                retries = retries - 1
1259*9c5db199SXin Li                actual_power_state = self.get_power_state()
1260*9c5db199SXin Li                if last_power_state != actual_power_state:
1261*9c5db199SXin Li                    logging.info("power state: %s", actual_power_state)
1262*9c5db199SXin Li                if actual_power_state is None:
1263*9c5db199SXin Li                    continue
1264*9c5db199SXin Li                if self._check_power_state(power_state, actual_power_state):
1265*9c5db199SXin Li                    return True
1266*9c5db199SXin Li                last_power_state = actual_power_state
1267*9c5db199SXin Li            except (error.TestFail, expat.ExpatError):
1268*9c5db199SXin Li                pass
1269*9c5db199SXin Li            delay_time = retry_delay - time.time() + start_time
1270*9c5db199SXin Li            if delay_time > 0:
1271*9c5db199SXin Li                time.sleep(delay_time)
1272*9c5db199SXin Li        return False
1273*9c5db199SXin Li
1274*9c5db199SXin Li    def run_shutdown_cmd(self, wait_for_offline=True):
1275*9c5db199SXin Li        """Shut down the DUT by running '/sbin/shutdown -P now'."""
1276*9c5db199SXin Li        self.faft_client.disconnect()
1277*9c5db199SXin Li        # Shut down in the background after sleeping so the call gets a reply.
1278*9c5db199SXin Li        try:
1279*9c5db199SXin Li            self._client.run_background('sleep 0.5; /sbin/shutdown -P now')
1280*9c5db199SXin Li        except error.AutoservRunError as e:
1281*9c5db199SXin Li            # From the ssh man page, error code 255 indicates ssh errors.
1282*9c5db199SXin Li            if e.result_obj.exit_status == 255:
1283*9c5db199SXin Li                logging.warning("Ignoring error from ssh: %s", e)
1284*9c5db199SXin Li            else:
1285*9c5db199SXin Li                raise
1286*9c5db199SXin Li        if wait_for_offline:
1287*9c5db199SXin Li            self.switcher.wait_for_client_offline()
1288*9c5db199SXin Li        self._client.close_main_ssh()
1289*9c5db199SXin Li
1290*9c5db199SXin Li    def suspend(self):
1291*9c5db199SXin Li        """Suspends the DUT."""
1292*9c5db199SXin Li        cmd = 'sleep %d; powerd_dbus_suspend' % self.EC_SUSPEND_DELAY
1293*9c5db199SXin Li        block = False
1294*9c5db199SXin Li        self.faft_client.system.run_shell_command(cmd, block)
1295*9c5db199SXin Li        time.sleep(self.EC_SUSPEND_DELAY)
1296*9c5db199SXin Li
1297*9c5db199SXin Li    def _setup_gbb_flags(self):
1298*9c5db199SXin Li        """Setup the GBB flags for FAFT test."""
1299*9c5db199SXin Li        if self.check_setup_done('gbb_flags'):
1300*9c5db199SXin Li            return
1301*9c5db199SXin Li
1302*9c5db199SXin Li        logging.info('Setting proper GBB flags for test.')
1303*9c5db199SXin Li        # Ensure that GBB flags are set to 0x140.
1304*9c5db199SXin Li        flags_to_set = (vboot.GBB_FLAG_RUNNING_FAFT |
1305*9c5db199SXin Li                        vboot.GBB_FLAG_ENTER_TRIGGERS_TONORM)
1306*9c5db199SXin Li        # And if the "no_ec_sync" argument is set, then disable EC software
1307*9c5db199SXin Li        # sync.
1308*9c5db199SXin Li        if self._no_ec_sync:
1309*9c5db199SXin Li            logging.info(
1310*9c5db199SXin Li                    'User selected to disable EC software sync')
1311*9c5db199SXin Li            flags_to_set |= vboot.GBB_FLAG_DISABLE_EC_SOFTWARE_SYNC
1312*9c5db199SXin Li
1313*9c5db199SXin Li        # And if the "no_fw_rollback_check" argument is set, then disable fw
1314*9c5db199SXin Li        # rollback check.
1315*9c5db199SXin Li        if self._no_fw_rollback_check:
1316*9c5db199SXin Li            logging.info(
1317*9c5db199SXin Li                    'User selected to disable FW rollback check')
1318*9c5db199SXin Li            flags_to_set |= vboot.GBB_FLAG_DISABLE_FW_ROLLBACK_CHECK
1319*9c5db199SXin Li
1320*9c5db199SXin Li        self.clear_set_gbb_flags(0xffffffff, flags_to_set)
1321*9c5db199SXin Li        self.mark_setup_done('gbb_flags')
1322*9c5db199SXin Li
1323*9c5db199SXin Li    def _restore_gbb_flags(self):
1324*9c5db199SXin Li        """Restore GBB flags to their original state."""
1325*9c5db199SXin Li        if self._backup_gbb_flags is None:
1326*9c5db199SXin Li            return
1327*9c5db199SXin Li        # Setting up and restoring the GBB flags take a lot of time. For
1328*9c5db199SXin Li        # speed-up purpose, don't restore it.
1329*9c5db199SXin Li        logging.info('***')
1330*9c5db199SXin Li        logging.info('*** Please manually restore the original GBB flags to: '
1331*9c5db199SXin Li                     '0x%x ***', self._backup_gbb_flags)
1332*9c5db199SXin Li        logging.info('***')
1333*9c5db199SXin Li        self.unmark_setup_done('gbb_flags')
1334*9c5db199SXin Li
1335*9c5db199SXin Li    def setup_tried_fwb(self, tried_fwb):
1336*9c5db199SXin Li        """Setup for fw B tried state.
1337*9c5db199SXin Li
1338*9c5db199SXin Li        It makes sure the system in the requested fw B tried state. If not, it
1339*9c5db199SXin Li        tries to do so.
1340*9c5db199SXin Li
1341*9c5db199SXin Li        @param tried_fwb: True if requested in tried_fwb=1;
1342*9c5db199SXin Li                          False if tried_fwb=0.
1343*9c5db199SXin Li        """
1344*9c5db199SXin Li        if tried_fwb:
1345*9c5db199SXin Li            if not self.checkers.crossystem_checker({'tried_fwb': '1'}):
1346*9c5db199SXin Li                logging.info(
1347*9c5db199SXin Li                    'Firmware is not booted with tried_fwb. Reboot into it.')
1348*9c5db199SXin Li                self.faft_client.system.set_try_fw_b()
1349*9c5db199SXin Li        else:
1350*9c5db199SXin Li            if not self.checkers.crossystem_checker({'tried_fwb': '0'}):
1351*9c5db199SXin Li                logging.info(
1352*9c5db199SXin Li                    'Firmware is booted with tried_fwb. Reboot to clear.')
1353*9c5db199SXin Li
1354*9c5db199SXin Li    def power_on(self):
1355*9c5db199SXin Li        """Switch DUT AC power on."""
1356*9c5db199SXin Li        self._client.power_on(self.power_control)
1357*9c5db199SXin Li
1358*9c5db199SXin Li    def power_off(self):
1359*9c5db199SXin Li        """Switch DUT AC power off."""
1360*9c5db199SXin Li        self._client.power_off(self.power_control)
1361*9c5db199SXin Li
1362*9c5db199SXin Li    def power_cycle(self):
1363*9c5db199SXin Li        """Power cycle DUT AC power."""
1364*9c5db199SXin Li        self._client.power_cycle(self.power_control)
1365*9c5db199SXin Li
1366*9c5db199SXin Li    def setup_rw_boot(self, section='a'):
1367*9c5db199SXin Li        """Make sure firmware is in RW-boot mode.
1368*9c5db199SXin Li
1369*9c5db199SXin Li        If the given firmware section is in RO-boot mode, turn off the RO-boot
1370*9c5db199SXin Li        flag and reboot DUT into RW-boot mode.
1371*9c5db199SXin Li
1372*9c5db199SXin Li        @param section: A firmware section, either 'a' or 'b'.
1373*9c5db199SXin Li        """
1374*9c5db199SXin Li        flags = self.faft_client.bios.get_preamble_flags(section)
1375*9c5db199SXin Li        if flags & vboot.PREAMBLE_USE_RO_NORMAL:
1376*9c5db199SXin Li            flags = flags ^ vboot.PREAMBLE_USE_RO_NORMAL
1377*9c5db199SXin Li            self.faft_client.bios.set_preamble_flags(section, flags)
1378*9c5db199SXin Li            self.switcher.mode_aware_reboot()
1379*9c5db199SXin Li
1380*9c5db199SXin Li    def setup_kernel(self, part):
1381*9c5db199SXin Li        """Setup for kernel test.
1382*9c5db199SXin Li
1383*9c5db199SXin Li        It makes sure both kernel A and B bootable and the current boot is
1384*9c5db199SXin Li        the requested kernel part.
1385*9c5db199SXin Li
1386*9c5db199SXin Li        @param part: A string of kernel partition number or 'a'/'b'.
1387*9c5db199SXin Li        """
1388*9c5db199SXin Li        self.ensure_kernel_boot(part)
1389*9c5db199SXin Li        logging.info('Checking the integrity of kernel B and rootfs B...')
1390*9c5db199SXin Li        if (self.faft_client.kernel.diff_a_b() or
1391*9c5db199SXin Li                not self.faft_client.rootfs.verify_rootfs('B')):
1392*9c5db199SXin Li            logging.info('Copying kernel and rootfs from A to B...')
1393*9c5db199SXin Li            self.copy_kernel_and_rootfs(from_part=part,
1394*9c5db199SXin Li                                        to_part=self.OTHER_KERNEL_MAP[part])
1395*9c5db199SXin Li        self.reset_and_prioritize_kernel(part)
1396*9c5db199SXin Li
1397*9c5db199SXin Li    def reset_and_prioritize_kernel(self, part):
1398*9c5db199SXin Li        """Make the requested partition highest priority.
1399*9c5db199SXin Li
1400*9c5db199SXin Li        This function also reset kerenl A and B to bootable.
1401*9c5db199SXin Li
1402*9c5db199SXin Li        @param part: A string of partition number to be prioritized.
1403*9c5db199SXin Li        """
1404*9c5db199SXin Li        root_dev = self.faft_client.system.get_root_dev()
1405*9c5db199SXin Li        # Reset kernel A and B to bootable.
1406*9c5db199SXin Li        self.faft_client.system.run_shell_command(
1407*9c5db199SXin Li            'cgpt add -i%s -P1 -S1 -T0 %s' % (self.KERNEL_MAP['a'], root_dev))
1408*9c5db199SXin Li        self.faft_client.system.run_shell_command(
1409*9c5db199SXin Li            'cgpt add -i%s -P1 -S1 -T0 %s' % (self.KERNEL_MAP['b'], root_dev))
1410*9c5db199SXin Li        # Set kernel part highest priority.
1411*9c5db199SXin Li        self.faft_client.system.run_shell_command('cgpt prioritize -i%s %s' %
1412*9c5db199SXin Li                (self.KERNEL_MAP[part], root_dev))
1413*9c5db199SXin Li
1414*9c5db199SXin Li    def do_blocking_sync(self, device):
1415*9c5db199SXin Li        """Run a blocking sync command."""
1416*9c5db199SXin Li        logging.debug("Blocking sync for %s", device)
1417*9c5db199SXin Li
1418*9c5db199SXin Li        if 'mmcblk' in device:
1419*9c5db199SXin Li            # For mmc devices, use `mmc status get` command to send an
1420*9c5db199SXin Li            # empty command to wait for the disk to be available again.
1421*9c5db199SXin Li            self.faft_client.system.run_shell_command('mmc status get %s' %
1422*9c5db199SXin Li                                                      device)
1423*9c5db199SXin Li        elif 'nvme' in device:
1424*9c5db199SXin Li            # For NVMe devices, use `nvme flush` command to commit data
1425*9c5db199SXin Li            # and metadata to non-volatile media.
1426*9c5db199SXin Li
1427*9c5db199SXin Li            # Get a list of NVMe namespaces, and flush them individually.
1428*9c5db199SXin Li            # The output is assumed to be in the following format:
1429*9c5db199SXin Li            # [ 0]:0x1
1430*9c5db199SXin Li            # [ 1]:0x2
1431*9c5db199SXin Li            list_ns_cmd = "nvme list-ns %s" % device
1432*9c5db199SXin Li            available_ns = self.faft_client.system.run_shell_command_get_output(
1433*9c5db199SXin Li                list_ns_cmd)
1434*9c5db199SXin Li
1435*9c5db199SXin Li            if not available_ns:
1436*9c5db199SXin Li                raise error.TestError(
1437*9c5db199SXin Li                    "Listing namespaces failed (empty output): %s"
1438*9c5db199SXin Li                    % list_ns_cmd)
1439*9c5db199SXin Li
1440*9c5db199SXin Li            for ns in available_ns:
1441*9c5db199SXin Li                ns = ns.split(':')[-1]
1442*9c5db199SXin Li                flush_cmd = 'nvme flush %s -n %s' % (device, ns)
1443*9c5db199SXin Li                flush_rc = self.faft_client.system.run_shell_command_get_status(
1444*9c5db199SXin Li                    flush_cmd)
1445*9c5db199SXin Li                if flush_rc != 0:
1446*9c5db199SXin Li                    raise error.TestError(
1447*9c5db199SXin Li                        "Flushing namespace %s failed (rc=%s): %s"
1448*9c5db199SXin Li                        % (ns, flush_rc, flush_cmd))
1449*9c5db199SXin Li        else:
1450*9c5db199SXin Li            # For other devices, hdparm sends TUR to check if
1451*9c5db199SXin Li            # a device is ready for transfer operation.
1452*9c5db199SXin Li            self.faft_client.system.run_shell_command('hdparm -f %s' % device)
1453*9c5db199SXin Li
1454*9c5db199SXin Li    def blocking_sync(self, freeze_for_reset=False):
1455*9c5db199SXin Li        """Sync root device and internal device, via script if possible.
1456*9c5db199SXin Li
1457*9c5db199SXin Li        The actual calls end up logged by the run() call, since they're printed
1458*9c5db199SXin Li        to stdout/stderr in the script.
1459*9c5db199SXin Li
1460*9c5db199SXin Li        @param freeze_for_reset: if True, prepare for reset by blocking writes
1461*9c5db199SXin Li                                 (only if enable_fs_sync_fsfreeze=True)
1462*9c5db199SXin Li        """
1463*9c5db199SXin Li
1464*9c5db199SXin Li        if self._use_sync_script:
1465*9c5db199SXin Li            if freeze_for_reset:
1466*9c5db199SXin Li                self.faft_client.quit()
1467*9c5db199SXin Li            try:
1468*9c5db199SXin Li                return self._client.blocking_sync(freeze_for_reset)
1469*9c5db199SXin Li            except (AttributeError, ImportError, error.AutoservRunError) as e:
1470*9c5db199SXin Li                logging.warning(
1471*9c5db199SXin Li                        'Falling back to old sync method due to error: %s', e)
1472*9c5db199SXin Li
1473*9c5db199SXin Li        # The double calls to sync fakes a blocking call
1474*9c5db199SXin Li        # since the first call returns before the flush
1475*9c5db199SXin Li        # is complete, but the second will wait for the
1476*9c5db199SXin Li        # first to finish.
1477*9c5db199SXin Li        self.faft_client.system.run_shell_command('sync')
1478*9c5db199SXin Li        self.faft_client.system.run_shell_command('sync')
1479*9c5db199SXin Li
1480*9c5db199SXin Li        # sync only sends SYNCHRONIZE_CACHE but doesn't check the status.
1481*9c5db199SXin Li        # This function will perform a device-specific sync command.
1482*9c5db199SXin Li        root_dev = self.faft_client.system.get_root_dev()
1483*9c5db199SXin Li        self.do_blocking_sync(root_dev)
1484*9c5db199SXin Li
1485*9c5db199SXin Li        # Also sync the internal device if booted from removable media.
1486*9c5db199SXin Li        if self.faft_client.system.is_removable_device_boot():
1487*9c5db199SXin Li            internal_dev = self.faft_client.system.get_internal_device()
1488*9c5db199SXin Li            self.do_blocking_sync(internal_dev)
1489*9c5db199SXin Li
1490*9c5db199SXin Li    def sync_and_ec_reboot(self, flags='', extra_sleep=0):
1491*9c5db199SXin Li        """Request the client sync and do a EC triggered reboot.
1492*9c5db199SXin Li
1493*9c5db199SXin Li        @param flags: Optional, a space-separated string of flags passed to EC
1494*9c5db199SXin Li                      reboot command, including:
1495*9c5db199SXin Li                          default: EC soft reboot;
1496*9c5db199SXin Li                          'hard': EC hard reboot.
1497*9c5db199SXin Li                          'cold': Cold reboot via servo.
1498*9c5db199SXin Li        @param extra_sleep: Optional, int or float for extra wait time for EC
1499*9c5db199SXin Li                            reboot in seconds.
1500*9c5db199SXin Li        """
1501*9c5db199SXin Li        self.blocking_sync(freeze_for_reset=True)
1502*9c5db199SXin Li        if flags == 'cold':
1503*9c5db199SXin Li            self.servo.get_power_state_controller().reset()
1504*9c5db199SXin Li        else:
1505*9c5db199SXin Li            self.ec.reboot(flags)
1506*9c5db199SXin Li        time.sleep(self.faft_config.ec_boot_to_console + extra_sleep)
1507*9c5db199SXin Li        self.check_lid_and_power_on()
1508*9c5db199SXin Li
1509*9c5db199SXin Li    def reboot_and_reset_tpm(self):
1510*9c5db199SXin Li        """Reboot into recovery mode, reset TPM, then reboot back to disk."""
1511*9c5db199SXin Li        self.switcher.reboot_to_mode(to_mode='rec')
1512*9c5db199SXin Li        self.faft_client.system.run_shell_command('chromeos-tpm-recovery')
1513*9c5db199SXin Li        self.switcher.mode_aware_reboot()
1514*9c5db199SXin Li
1515*9c5db199SXin Li    def full_power_off_and_on(self):
1516*9c5db199SXin Li        """Shutdown the device by pressing power button and power on again."""
1517*9c5db199SXin Li        boot_id = self.get_bootid()
1518*9c5db199SXin Li        self.faft_client.disconnect()
1519*9c5db199SXin Li
1520*9c5db199SXin Li        # Press power button to trigger ChromeOS normal shutdown process.
1521*9c5db199SXin Li        # We use a customized delay since the normal-press 1.2s is not enough.
1522*9c5db199SXin Li        self.servo.power_key(self.faft_config.hold_pwr_button_poweroff)
1523*9c5db199SXin Li        # device can take 44-51 seconds to restart,
1524*9c5db199SXin Li        # add buffer from the default timeout of 60 seconds.
1525*9c5db199SXin Li        self.switcher.wait_for_client_offline(timeout=100, orig_boot_id=boot_id)
1526*9c5db199SXin Li        time.sleep(self.faft_config.shutdown)
1527*9c5db199SXin Li        if self.faft_config.chrome_ec:
1528*9c5db199SXin Li            self.check_shutdown_power_state(self.POWER_STATE_G3,
1529*9c5db199SXin Li                                            orig_boot_id=boot_id)
1530*9c5db199SXin Li        # Short press power button to boot DUT again.
1531*9c5db199SXin Li        self.servo.power_key(self.faft_config.hold_pwr_button_poweron)
1532*9c5db199SXin Li
1533*9c5db199SXin Li    def check_shutdown_power_state(self, power_state,
1534*9c5db199SXin Li                                   pwr_retries=DEFAULT_PWR_RETRIES,
1535*9c5db199SXin Li                                   orig_boot_id=None):
1536*9c5db199SXin Li        """Check whether the device shut down and entered the given power state.
1537*9c5db199SXin Li
1538*9c5db199SXin Li        If orig_boot_id is specified, it will check whether the DUT responds to
1539*9c5db199SXin Li        ssh requests, then use orig_boot_id to check if it rebooted.
1540*9c5db199SXin Li
1541*9c5db199SXin Li        @param power_state: EC power state has to be checked. Either S5 or G3.
1542*9c5db199SXin Li        @param pwr_retries: Times to check if the DUT in expected power state.
1543*9c5db199SXin Li        @param orig_boot_id: Old boot_id, to check for unexpected reboots.
1544*9c5db199SXin Li        @raise TestFail: If device failed to enter into requested power state.
1545*9c5db199SXin Li        """
1546*9c5db199SXin Li        if not self.wait_power_state(power_state, pwr_retries):
1547*9c5db199SXin Li            current_state = self.get_power_state()
1548*9c5db199SXin Li            if current_state == self.POWER_STATE_S0 and self._client.wait_up():
1549*9c5db199SXin Li                # DUT is unexpectedly up, so check whether it rebooted instead.
1550*9c5db199SXin Li                new_boot_id = self.get_bootid()
1551*9c5db199SXin Li                logging.debug('orig_boot_id=%s, new_boot_id=%s',
1552*9c5db199SXin Li                              orig_boot_id, new_boot_id)
1553*9c5db199SXin Li                if orig_boot_id is None or new_boot_id is None:
1554*9c5db199SXin Li                    # Can't say anything more specific without values to compare
1555*9c5db199SXin Li                    raise error.TestFail(
1556*9c5db199SXin Li                            "Expected state %s, but the system is unexpectedly"
1557*9c5db199SXin Li                            " still up.  Current state: %s"
1558*9c5db199SXin Li                            % (power_state, current_state))
1559*9c5db199SXin Li                if new_boot_id == orig_boot_id:
1560*9c5db199SXin Li                    raise error.TestFail(
1561*9c5db199SXin Li                            "Expected state %s, but the system didn't shut"
1562*9c5db199SXin Li                            " down.  Current state: %s"
1563*9c5db199SXin Li                            % (power_state, current_state))
1564*9c5db199SXin Li                else:
1565*9c5db199SXin Li                    raise error.TestFail(
1566*9c5db199SXin Li                            "Expected state %s, but the system rebooted instead"
1567*9c5db199SXin Li                            " of shutting down.  Current state: %s"
1568*9c5db199SXin Li                            % (power_state, current_state))
1569*9c5db199SXin Li
1570*9c5db199SXin Li            if current_state is None:
1571*9c5db199SXin Li                current_state = '(unknown)'
1572*9c5db199SXin Li
1573*9c5db199SXin Li            if current_state == power_state:
1574*9c5db199SXin Li                raise error.TestFail(
1575*9c5db199SXin Li                        "Expected state %s, but the system didn't reach it"
1576*9c5db199SXin Li                        " until after the limit of %s tries."
1577*9c5db199SXin Li                        % (power_state, pwr_retries))
1578*9c5db199SXin Li
1579*9c5db199SXin Li            raise error.TestFail('System not shutdown properly and EC fails'
1580*9c5db199SXin Li                                 ' to enter into %s state.  Current state: %s'
1581*9c5db199SXin Li                                 % (power_state, current_state))
1582*9c5db199SXin Li        logging.info('System entered into %s state..', power_state)
1583*9c5db199SXin Li
1584*9c5db199SXin Li    def check_lid_and_power_on(self):
1585*9c5db199SXin Li        """
1586*9c5db199SXin Li        On devices with EC software sync, system powers on after EC reboots if
1587*9c5db199SXin Li        lid is open. Otherwise, the EC shuts down CPU after about 3 seconds.
1588*9c5db199SXin Li        This method checks lid switch state and presses power button if
1589*9c5db199SXin Li        necessary.
1590*9c5db199SXin Li        """
1591*9c5db199SXin Li        if self.servo.get("lid_open") == "no":
1592*9c5db199SXin Li            time.sleep(self.faft_config.software_sync)
1593*9c5db199SXin Li            self.servo.power_short_press()
1594*9c5db199SXin Li
1595*9c5db199SXin Li    def stop_powerd(self):
1596*9c5db199SXin Li        """Stop the powerd daemon on the AP.
1597*9c5db199SXin Li
1598*9c5db199SXin Li        This will cause the AP to ignore power button presses sent by the EC.
1599*9c5db199SXin Li        """
1600*9c5db199SXin Li        powerd_running = self.faft_client.system.run_shell_command_check_output(
1601*9c5db199SXin Li                'status powerd', 'start/running')
1602*9c5db199SXin Li        if powerd_running:
1603*9c5db199SXin Li            logging.debug('Stopping powerd')
1604*9c5db199SXin Li            self.faft_client.system.run_shell_command("stop powerd")
1605*9c5db199SXin Li
1606*9c5db199SXin Li    def _modify_usb_kernel(self, usb_dev, from_magic, to_magic):
1607*9c5db199SXin Li        """Modify the kernel header magic in USB stick.
1608*9c5db199SXin Li
1609*9c5db199SXin Li        The kernel header magic is the first 8-byte of kernel partition.
1610*9c5db199SXin Li        We modify it to make it fail on kernel verification check.
1611*9c5db199SXin Li
1612*9c5db199SXin Li        @param usb_dev: A string of USB stick path on the host, like '/dev/sdc'.
1613*9c5db199SXin Li        @param from_magic: A string of magic which we change it from.
1614*9c5db199SXin Li        @param to_magic: A string of magic which we change it to.
1615*9c5db199SXin Li        @raise TestError: if failed to change magic.
1616*9c5db199SXin Li        """
1617*9c5db199SXin Li        assert len(from_magic) == 8
1618*9c5db199SXin Li        assert len(to_magic) == 8
1619*9c5db199SXin Li        # USB image only contains one kernel.
1620*9c5db199SXin Li        kernel_part = self._join_part(usb_dev, self.KERNEL_MAP['a'])
1621*9c5db199SXin Li        read_cmd = "sudo dd if=%s bs=8 count=1 2>/dev/null" % kernel_part
1622*9c5db199SXin Li        current_magic = self.servo.system_output(read_cmd)
1623*9c5db199SXin Li        if current_magic == to_magic:
1624*9c5db199SXin Li            logging.info("The kernel magic is already %s.", current_magic)
1625*9c5db199SXin Li            return
1626*9c5db199SXin Li        if current_magic != from_magic:
1627*9c5db199SXin Li            raise error.TestError("Invalid kernel image on USB: wrong magic.")
1628*9c5db199SXin Li
1629*9c5db199SXin Li        logging.info('Modify the kernel magic in USB, from %s to %s.',
1630*9c5db199SXin Li                     from_magic, to_magic)
1631*9c5db199SXin Li        write_cmd = ("echo -n '%s' | sudo dd of=%s oflag=sync conv=notrunc "
1632*9c5db199SXin Li                     " 2>/dev/null" % (to_magic, kernel_part))
1633*9c5db199SXin Li        self.servo.system(write_cmd)
1634*9c5db199SXin Li
1635*9c5db199SXin Li        if self.servo.system_output(read_cmd) != to_magic:
1636*9c5db199SXin Li            raise error.TestError("Failed to write new magic.")
1637*9c5db199SXin Li
1638*9c5db199SXin Li    def corrupt_usb_kernel(self, usb_dev):
1639*9c5db199SXin Li        """Corrupt USB kernel by modifying its magic from CHROMEOS to CORRUPTD.
1640*9c5db199SXin Li
1641*9c5db199SXin Li        @param usb_dev: A string of USB stick path on the host, like '/dev/sdc'.
1642*9c5db199SXin Li        """
1643*9c5db199SXin Li        self._modify_usb_kernel(usb_dev, self.CHROMEOS_MAGIC,
1644*9c5db199SXin Li                                self.CORRUPTED_MAGIC)
1645*9c5db199SXin Li
1646*9c5db199SXin Li    def restore_usb_kernel(self, usb_dev):
1647*9c5db199SXin Li        """Restore USB kernel by modifying its magic from CORRUPTD to CHROMEOS.
1648*9c5db199SXin Li
1649*9c5db199SXin Li        @param usb_dev: A string of USB stick path on the host, like '/dev/sdc'.
1650*9c5db199SXin Li        """
1651*9c5db199SXin Li        self._modify_usb_kernel(usb_dev, self.CORRUPTED_MAGIC,
1652*9c5db199SXin Li                                self.CHROMEOS_MAGIC)
1653*9c5db199SXin Li
1654*9c5db199SXin Li    def _call_action(self, action_tuple, check_status=False):
1655*9c5db199SXin Li        """Call the action function with/without arguments.
1656*9c5db199SXin Li
1657*9c5db199SXin Li        @param action_tuple: A function, or a tuple (function, args, error_msg),
1658*9c5db199SXin Li                             in which, args and error_msg are optional. args is
1659*9c5db199SXin Li                             either a value or a tuple if multiple arguments.
1660*9c5db199SXin Li                             This can also be a list containing multiple
1661*9c5db199SXin Li                             function or tuple. In this case, these actions are
1662*9c5db199SXin Li                             called in sequence.
1663*9c5db199SXin Li        @param check_status: Check the return value of action function. If not
1664*9c5db199SXin Li                             succeed, raises a TestFail exception.
1665*9c5db199SXin Li        @return: The result value of the action function.
1666*9c5db199SXin Li        @raise TestError: An error when the action function is not callable.
1667*9c5db199SXin Li        @raise TestFail: When check_status=True, action function not succeed.
1668*9c5db199SXin Li        """
1669*9c5db199SXin Li        if isinstance(action_tuple, list):
1670*9c5db199SXin Li            return all([self._call_action(action, check_status=check_status)
1671*9c5db199SXin Li                        for action in action_tuple])
1672*9c5db199SXin Li
1673*9c5db199SXin Li        action = action_tuple
1674*9c5db199SXin Li        args = ()
1675*9c5db199SXin Li        error_msg = 'Not succeed'
1676*9c5db199SXin Li        if isinstance(action_tuple, tuple):
1677*9c5db199SXin Li            action = action_tuple[0]
1678*9c5db199SXin Li            if len(action_tuple) >= 2:
1679*9c5db199SXin Li                args = action_tuple[1]
1680*9c5db199SXin Li                if not isinstance(args, tuple):
1681*9c5db199SXin Li                    args = (args,)
1682*9c5db199SXin Li            if len(action_tuple) >= 3:
1683*9c5db199SXin Li                error_msg = action_tuple[2]
1684*9c5db199SXin Li
1685*9c5db199SXin Li        if action is None:
1686*9c5db199SXin Li            return
1687*9c5db199SXin Li
1688*9c5db199SXin Li        if not callable(action):
1689*9c5db199SXin Li            raise error.TestError('action is not callable!')
1690*9c5db199SXin Li
1691*9c5db199SXin Li        info_msg = 'calling %s' % action.__name__
1692*9c5db199SXin Li        if args:
1693*9c5db199SXin Li            info_msg += ' with args %s' % str(args)
1694*9c5db199SXin Li        logging.info(info_msg)
1695*9c5db199SXin Li        ret = action(*args)
1696*9c5db199SXin Li
1697*9c5db199SXin Li        if check_status and not ret:
1698*9c5db199SXin Li            raise error.TestFail('%s: %s returning %s' %
1699*9c5db199SXin Li                                 (error_msg, info_msg, str(ret)))
1700*9c5db199SXin Li        return ret
1701*9c5db199SXin Li
1702*9c5db199SXin Li    def run_shutdown_process(self, shutdown_action, pre_power_action=None,
1703*9c5db199SXin Li                             run_power_action=True, post_power_action=None,
1704*9c5db199SXin Li                             shutdown_timeout=None):
1705*9c5db199SXin Li        """Run shutdown_action(), which makes DUT shutdown, and power it on.
1706*9c5db199SXin Li
1707*9c5db199SXin Li        @param shutdown_action: function which makes DUT shutdown, like
1708*9c5db199SXin Li                                pressing power key.
1709*9c5db199SXin Li        @param pre_power_action: function which is called before next power on.
1710*9c5db199SXin Li        @param run_power_action: power_key press by default, set to None to skip.
1711*9c5db199SXin Li        @param post_power_action: function which is called after next power on.
1712*9c5db199SXin Li        @param shutdown_timeout: a timeout to confirm DUT shutdown.
1713*9c5db199SXin Li        @raise TestFail: if the shutdown_action() failed to turn DUT off.
1714*9c5db199SXin Li        """
1715*9c5db199SXin Li        self._call_action(shutdown_action)
1716*9c5db199SXin Li        logging.info('Wait to ensure DUT shut down...')
1717*9c5db199SXin Li        try:
1718*9c5db199SXin Li            if shutdown_timeout is None:
1719*9c5db199SXin Li                shutdown_timeout = self.faft_config.shutdown_timeout
1720*9c5db199SXin Li            self.switcher.wait_for_client(timeout=shutdown_timeout)
1721*9c5db199SXin Li            raise error.TestFail(
1722*9c5db199SXin Li                    'Should shut the device down after calling %s.' %
1723*9c5db199SXin Li                    shutdown_action.__name__)
1724*9c5db199SXin Li        except ConnectionError:
1725*9c5db199SXin Li            if self.faft_config.chrome_ec:
1726*9c5db199SXin Li                self.check_shutdown_power_state(self.POWER_STATE_G3)
1727*9c5db199SXin Li            logging.info(
1728*9c5db199SXin Li                'DUT is surely shutdown. We are going to power it on again...')
1729*9c5db199SXin Li
1730*9c5db199SXin Li        if pre_power_action:
1731*9c5db199SXin Li            self._call_action(pre_power_action)
1732*9c5db199SXin Li        if run_power_action:
1733*9c5db199SXin Li            self.servo.power_key(self.faft_config.hold_pwr_button_poweron)
1734*9c5db199SXin Li        if post_power_action:
1735*9c5db199SXin Li            self._call_action(post_power_action)
1736*9c5db199SXin Li
1737*9c5db199SXin Li    def get_bootid(self, retry=3):
1738*9c5db199SXin Li        """
1739*9c5db199SXin Li        Return the bootid.
1740*9c5db199SXin Li        """
1741*9c5db199SXin Li        boot_id = None
1742*9c5db199SXin Li        while retry:
1743*9c5db199SXin Li            try:
1744*9c5db199SXin Li                boot_id = self._client.get_boot_id()
1745*9c5db199SXin Li                break
1746*9c5db199SXin Li            except error.AutoservRunError:
1747*9c5db199SXin Li                retry -= 1
1748*9c5db199SXin Li                if retry:
1749*9c5db199SXin Li                    logging.debug('Retry to get boot_id...')
1750*9c5db199SXin Li                else:
1751*9c5db199SXin Li                    logging.warning('Failed to get boot_id.')
1752*9c5db199SXin Li        logging.debug('boot_id: %s', boot_id)
1753*9c5db199SXin Li        return boot_id
1754*9c5db199SXin Li
1755*9c5db199SXin Li    def check_state(self, func):
1756*9c5db199SXin Li        """
1757*9c5db199SXin Li        Wrapper around _call_action with check_status set to True. This is a
1758*9c5db199SXin Li        helper function to be used by tests and is currently implemented by
1759*9c5db199SXin Li        calling _call_action with check_status=True.
1760*9c5db199SXin Li
1761*9c5db199SXin Li        TODO: This function's arguments need to be made more stringent. And
1762*9c5db199SXin Li        its functionality should be moved over to check functions directly in
1763*9c5db199SXin Li        the future.
1764*9c5db199SXin Li
1765*9c5db199SXin Li        @param func: A function, or a tuple (function, args, error_msg),
1766*9c5db199SXin Li                             in which, args and error_msg are optional. args is
1767*9c5db199SXin Li                             either a value or a tuple if multiple arguments.
1768*9c5db199SXin Li                             This can also be a list containing multiple
1769*9c5db199SXin Li                             function or tuple. In this case, these actions are
1770*9c5db199SXin Li                             called in sequence.
1771*9c5db199SXin Li        @return: The result value of the action function.
1772*9c5db199SXin Li        @raise TestFail: If the function does notsucceed.
1773*9c5db199SXin Li        """
1774*9c5db199SXin Li        self._call_action(func, check_status=True)
1775*9c5db199SXin Li
1776*9c5db199SXin Li    def get_current_firmware_identity(self):
1777*9c5db199SXin Li        """Get current firmware sha and fwids of body and vblock.
1778*9c5db199SXin Li
1779*9c5db199SXin Li        @return: Current firmware checksums and fwids, as a dict
1780*9c5db199SXin Li        """
1781*9c5db199SXin Li
1782*9c5db199SXin Li        current_checksums = {
1783*9c5db199SXin Li            'VBOOTA': self.faft_client.bios.get_sig_sha('a'),
1784*9c5db199SXin Li            'FVMAINA': self.faft_client.bios.get_body_sha('a'),
1785*9c5db199SXin Li            'VBOOTB': self.faft_client.bios.get_sig_sha('b'),
1786*9c5db199SXin Li            'FVMAINB': self.faft_client.bios.get_body_sha('b'),
1787*9c5db199SXin Li        }
1788*9c5db199SXin Li        if not all(current_checksums.values()):
1789*9c5db199SXin Li            raise error.TestError(
1790*9c5db199SXin Li                    'Failed to get firmware sha: %s', current_checksums)
1791*9c5db199SXin Li
1792*9c5db199SXin Li        current_fwids = {
1793*9c5db199SXin Li            'RO_FRID': self.faft_client.bios.get_section_fwid('ro'),
1794*9c5db199SXin Li            'RW_FWID_A': self.faft_client.bios.get_section_fwid('a'),
1795*9c5db199SXin Li            'RW_FWID_B': self.faft_client.bios.get_section_fwid('b'),
1796*9c5db199SXin Li        }
1797*9c5db199SXin Li        if not all(current_fwids.values()):
1798*9c5db199SXin Li            raise error.TestError(
1799*9c5db199SXin Li                    'Failed to get firmware fwid(s): %s', current_fwids)
1800*9c5db199SXin Li
1801*9c5db199SXin Li        identifying_info = dict(current_fwids)
1802*9c5db199SXin Li        identifying_info.update(current_checksums)
1803*9c5db199SXin Li        return identifying_info
1804*9c5db199SXin Li
1805*9c5db199SXin Li    def is_firmware_changed(self):
1806*9c5db199SXin Li        """Check if the current firmware changed, by comparing its SHA and fwid.
1807*9c5db199SXin Li
1808*9c5db199SXin Li        @return: True if it is changed, otherwise False.
1809*9c5db199SXin Li        """
1810*9c5db199SXin Li        # Device may not be rebooted after test.
1811*9c5db199SXin Li        self.faft_client.bios.reload()
1812*9c5db199SXin Li
1813*9c5db199SXin Li        current_info = self.get_current_firmware_identity()
1814*9c5db199SXin Li        prev_info = self._backup_firmware_identity
1815*9c5db199SXin Li
1816*9c5db199SXin Li        if current_info == prev_info:
1817*9c5db199SXin Li            return False
1818*9c5db199SXin Li        else:
1819*9c5db199SXin Li            changed = set()
1820*9c5db199SXin Li            for section in set(current_info.keys()) | set(prev_info.keys()):
1821*9c5db199SXin Li                if current_info.get(section) != prev_info.get(section):
1822*9c5db199SXin Li                    changed.add(section)
1823*9c5db199SXin Li
1824*9c5db199SXin Li            logging.info('Firmware changed: %s', ', '.join(sorted(changed)))
1825*9c5db199SXin Li            return True
1826*9c5db199SXin Li
1827*9c5db199SXin Li    def backup_firmware(self, suffix='.original'):
1828*9c5db199SXin Li        """Backup firmware to file, and then send it to host.
1829*9c5db199SXin Li
1830*9c5db199SXin Li        @param suffix: a string appended to backup file name
1831*9c5db199SXin Li        """
1832*9c5db199SXin Li        remote_temp_dir = self.faft_client.system.create_temp_dir()
1833*9c5db199SXin Li        remote_bios_path = os.path.join(remote_temp_dir, 'bios')
1834*9c5db199SXin Li        self.faft_client.bios.dump_whole(remote_bios_path)
1835*9c5db199SXin Li        self._client.get_file(remote_bios_path,
1836*9c5db199SXin Li                              os.path.join(self.resultsdir, 'bios' + suffix))
1837*9c5db199SXin Li
1838*9c5db199SXin Li        if self.faft_config.chrome_ec:
1839*9c5db199SXin Li            remote_ec_path = os.path.join(remote_temp_dir, 'ec')
1840*9c5db199SXin Li            self.faft_client.ec.dump_whole(remote_ec_path)
1841*9c5db199SXin Li            self._client.get_file(remote_ec_path,
1842*9c5db199SXin Li                              os.path.join(self.resultsdir, 'ec' + suffix))
1843*9c5db199SXin Li
1844*9c5db199SXin Li        self._client.run('rm -rf %s' % remote_temp_dir)
1845*9c5db199SXin Li        logging.info('Backup firmware stored in %s with suffix %s',
1846*9c5db199SXin Li            self.resultsdir, suffix)
1847*9c5db199SXin Li
1848*9c5db199SXin Li        self._backup_firmware_identity = self.get_current_firmware_identity()
1849*9c5db199SXin Li
1850*9c5db199SXin Li    def is_firmware_saved(self):
1851*9c5db199SXin Li        """Check if a firmware saved (called backup_firmware before).
1852*9c5db199SXin Li
1853*9c5db199SXin Li        @return: True if the firmware is backed up; otherwise False.
1854*9c5db199SXin Li        """
1855*9c5db199SXin Li        return bool(self._backup_firmware_identity)
1856*9c5db199SXin Li
1857*9c5db199SXin Li    def restore_firmware(self, suffix='.original', restore_ec=True,
1858*9c5db199SXin Li                         reboot_ec=False):
1859*9c5db199SXin Li        """Restore firmware from host in resultsdir.
1860*9c5db199SXin Li
1861*9c5db199SXin Li        @param suffix: a string appended to backup file name
1862*9c5db199SXin Li        @param restore_ec: True to restore the ec firmware; False not to do.
1863*9c5db199SXin Li        @param reboot_ec: True to reboot EC after restore (if it was restored)
1864*9c5db199SXin Li        @return: True if firmware needed to be restored
1865*9c5db199SXin Li        """
1866*9c5db199SXin Li        if not self.is_firmware_changed():
1867*9c5db199SXin Li            return False
1868*9c5db199SXin Li
1869*9c5db199SXin Li        # Backup current corrupted firmware.
1870*9c5db199SXin Li        self.backup_firmware(suffix='.corrupt')
1871*9c5db199SXin Li
1872*9c5db199SXin Li        # Restore firmware.
1873*9c5db199SXin Li        remote_temp_dir = self.faft_client.system.create_temp_dir()
1874*9c5db199SXin Li
1875*9c5db199SXin Li        bios_local = os.path.join(self.resultsdir, 'bios%s' % suffix)
1876*9c5db199SXin Li        bios_remote = os.path.join(remote_temp_dir, 'bios%s' % suffix)
1877*9c5db199SXin Li        self._client.send_file(bios_local, bios_remote)
1878*9c5db199SXin Li        self.faft_client.bios.write_whole(bios_remote)
1879*9c5db199SXin Li
1880*9c5db199SXin Li        if self.faft_config.chrome_ec and restore_ec:
1881*9c5db199SXin Li            ec_local = os.path.join(self.resultsdir, 'ec%s' % suffix)
1882*9c5db199SXin Li            ec_remote = os.path.join(remote_temp_dir, 'ec%s' % suffix)
1883*9c5db199SXin Li            self._client.send_file(ec_local, ec_remote)
1884*9c5db199SXin Li            ec_cmd = self.faft_client.ec.get_write_cmd(ec_remote)
1885*9c5db199SXin Li            try:
1886*9c5db199SXin Li                self._client.run(ec_cmd, timeout=300)
1887*9c5db199SXin Li            except error.AutoservSSHTimeout:
1888*9c5db199SXin Li                logging.warning("DUT connection died during EC restore")
1889*9c5db199SXin Li                self.faft_client.disconnect()
1890*9c5db199SXin Li
1891*9c5db199SXin Li            except error.GenericHostRunError:
1892*9c5db199SXin Li                logging.warning("DUT command failed during EC restore")
1893*9c5db199SXin Li                logging.debug("Full exception:", exc_info=True)
1894*9c5db199SXin Li            if reboot_ec:
1895*9c5db199SXin Li                self.switcher.mode_aware_reboot(
1896*9c5db199SXin Li                        'custom', lambda: self.sync_and_ec_reboot('hard'))
1897*9c5db199SXin Li            else:
1898*9c5db199SXin Li                self.switcher.mode_aware_reboot()
1899*9c5db199SXin Li        else:
1900*9c5db199SXin Li            self.switcher.mode_aware_reboot()
1901*9c5db199SXin Li        logging.info('Successfully restored firmware.')
1902*9c5db199SXin Li        return True
1903*9c5db199SXin Li
1904*9c5db199SXin Li    def setup_firmwareupdate_shellball(self, shellball=None):
1905*9c5db199SXin Li        """Setup a shellball to use in firmware update test.
1906*9c5db199SXin Li
1907*9c5db199SXin Li        Check if there is a given shellball, and it is a shell script. Then,
1908*9c5db199SXin Li        send it to the remote host. Otherwise, use the
1909*9c5db199SXin Li        /usr/sbin/chromeos-firmwareupdate in the image and replace its inside
1910*9c5db199SXin Li        BIOS and EC images with the active firmware images.
1911*9c5db199SXin Li
1912*9c5db199SXin Li        @param shellball: path of a shellball or default to None.
1913*9c5db199SXin Li        """
1914*9c5db199SXin Li        if shellball:
1915*9c5db199SXin Li            # Determine the firmware file is a shellball or a raw binary.
1916*9c5db199SXin Li            is_shellball = (utils.system_output("file %s" % shellball).find(
1917*9c5db199SXin Li                    "shell script") != -1)
1918*9c5db199SXin Li            if is_shellball:
1919*9c5db199SXin Li                logging.info('Device will update firmware with shellball %s',
1920*9c5db199SXin Li                             shellball)
1921*9c5db199SXin Li                temp_path = self.faft_client.updater.get_temp_path()
1922*9c5db199SXin Li                working_shellball = os.path.join(temp_path,
1923*9c5db199SXin Li                                                 'chromeos-firmwareupdate')
1924*9c5db199SXin Li                self._client.send_file(shellball, working_shellball)
1925*9c5db199SXin Li                self.faft_client.updater.extract_shellball()
1926*9c5db199SXin Li            else:
1927*9c5db199SXin Li                raise error.TestFail(
1928*9c5db199SXin Li                    'The given shellball is not a shell script.')
1929*9c5db199SXin Li        else:
1930*9c5db199SXin Li            logging.info('No shellball given, use the original shellball and '
1931*9c5db199SXin Li                         'replace its BIOS and EC images.')
1932*9c5db199SXin Li            work_path = self.faft_client.updater.get_work_path()
1933*9c5db199SXin Li            bios_in_work_path = os.path.join(
1934*9c5db199SXin Li                work_path, self.faft_client.updater.get_bios_relative_path())
1935*9c5db199SXin Li            ec_in_work_path = os.path.join(
1936*9c5db199SXin Li                work_path, self.faft_client.updater.get_ec_relative_path())
1937*9c5db199SXin Li            logging.info('Writing current BIOS to: %s', bios_in_work_path)
1938*9c5db199SXin Li            self.faft_client.bios.dump_whole(bios_in_work_path)
1939*9c5db199SXin Li            if self.faft_config.chrome_ec:
1940*9c5db199SXin Li                logging.info('Writing current EC to: %s', ec_in_work_path)
1941*9c5db199SXin Li                self.faft_client.ec.dump_firmware(ec_in_work_path)
1942*9c5db199SXin Li            self.faft_client.updater.repack_shellball()
1943*9c5db199SXin Li
1944*9c5db199SXin Li    def is_kernel_changed(self, kernel_type='KERN'):
1945*9c5db199SXin Li        """Check if the current kernel is changed, by comparing its SHA1 hash.
1946*9c5db199SXin Li
1947*9c5db199SXin Li        @param kernel_type: The type name of kernel ('KERN' or 'MINIOS').
1948*9c5db199SXin Li        @return: True if it is changed; otherwise, False.
1949*9c5db199SXin Li        """
1950*9c5db199SXin Li        changed = False
1951*9c5db199SXin Li        for p in ('A', 'B'):
1952*9c5db199SXin Li            backup_sha = self._backup_kernel_sha[kernel_type].get(p, None)
1953*9c5db199SXin Li            current_sha = self.kernel_servicer[kernel_type].get_sha(p)
1954*9c5db199SXin Li            if backup_sha != current_sha:
1955*9c5db199SXin Li                changed = True
1956*9c5db199SXin Li                logging.info('Kernel %s-%s is changed', kernel_type, p)
1957*9c5db199SXin Li        return changed
1958*9c5db199SXin Li
1959*9c5db199SXin Li    def backup_kernel(self, suffix='.original', kernel_type='KERN'):
1960*9c5db199SXin Li        """Backup kernel to files, and the send them to host.
1961*9c5db199SXin Li
1962*9c5db199SXin Li        @param kernel_type: The type name of kernel ('KERN' or 'MINIOS').
1963*9c5db199SXin Li        @param suffix: a string appended to backup file name.
1964*9c5db199SXin Li        """
1965*9c5db199SXin Li        kernel_name = self.KERNEL_TYPE_NAME_MAP[kernel_type]
1966*9c5db199SXin Li        servicer = self.kernel_servicer[kernel_type]
1967*9c5db199SXin Li        remote_temp_dir = self.faft_client.system.create_temp_dir()
1968*9c5db199SXin Li        for p in ('A', 'B'):
1969*9c5db199SXin Li            remote_path = os.path.join(remote_temp_dir,
1970*9c5db199SXin Li                                       '%s_%s' % (kernel_name, p))
1971*9c5db199SXin Li            servicer.dump(p, remote_path)
1972*9c5db199SXin Li            self._client.get_file(
1973*9c5db199SXin Li                    remote_path,
1974*9c5db199SXin Li                    os.path.join(self.resultsdir,
1975*9c5db199SXin Li                                 '%s_%s%s' % (kernel_name, p, suffix)))
1976*9c5db199SXin Li            self._backup_kernel_sha[kernel_type][p] = servicer.get_sha(p)
1977*9c5db199SXin Li        logging.info('Backup %s stored in %s with suffix %s', kernel_name,
1978*9c5db199SXin Li                     self.resultsdir, suffix)
1979*9c5db199SXin Li
1980*9c5db199SXin Li    def is_kernel_saved(self, kernel_type='KERN'):
1981*9c5db199SXin Li        """Check if kernel images are saved (backup_kernel called before).
1982*9c5db199SXin Li
1983*9c5db199SXin Li        @param kernel_type: The type name of kernel ('KERN' or 'MINIOS').
1984*9c5db199SXin Li        @return: True if the kernel is saved; otherwise, False.
1985*9c5db199SXin Li        """
1986*9c5db199SXin Li        return len(self._backup_kernel_sha[kernel_type]) != 0
1987*9c5db199SXin Li
1988*9c5db199SXin Li    def restore_kernel(self, suffix='.original', kernel_type='KERN'):
1989*9c5db199SXin Li        """Restore kernel from host in resultsdir.
1990*9c5db199SXin Li
1991*9c5db199SXin Li        @param kernel_type: The type name of kernel ('KERN' or 'MINIOS').
1992*9c5db199SXin Li        @param suffix: a string appended to backup file name.
1993*9c5db199SXin Li        @return: True if kernel needed to be restored
1994*9c5db199SXin Li        """
1995*9c5db199SXin Li        if not self.is_kernel_changed(kernel_type):
1996*9c5db199SXin Li            return False
1997*9c5db199SXin Li
1998*9c5db199SXin Li        # Backup current corrupted kernel.
1999*9c5db199SXin Li        self.backup_kernel(suffix='.corrupt', kernel_type=kernel_type)
2000*9c5db199SXin Li
2001*9c5db199SXin Li        # Restore kernel.
2002*9c5db199SXin Li        kernel_name = self.KERNEL_TYPE_NAME_MAP[kernel_type]
2003*9c5db199SXin Li        servicer = self.kernel_servicer[kernel_type]
2004*9c5db199SXin Li        remote_temp_dir = self.faft_client.system.create_temp_dir()
2005*9c5db199SXin Li        for p in ('A', 'B'):
2006*9c5db199SXin Li            remote_path = os.path.join(remote_temp_dir,
2007*9c5db199SXin Li                                       '%s_%s' % (kernel_name, p))
2008*9c5db199SXin Li            servicer.dump(p, remote_path)
2009*9c5db199SXin Li            self._client.send_file(
2010*9c5db199SXin Li                    os.path.join(self.resultsdir,
2011*9c5db199SXin Li                                 '%s_%s%s' % (kernel_name, p, suffix)),
2012*9c5db199SXin Li                    remote_path)
2013*9c5db199SXin Li            servicer.write(p, remote_path)
2014*9c5db199SXin Li
2015*9c5db199SXin Li        self.switcher.mode_aware_reboot()
2016*9c5db199SXin Li        logging.info('Successfully restored %s.', kernel_type)
2017*9c5db199SXin Li        return True
2018*9c5db199SXin Li
2019*9c5db199SXin Li    def backup_cgpt_attributes(self):
2020*9c5db199SXin Li        """Backup CGPT partition table attributes."""
2021*9c5db199SXin Li        self._backup_cgpt_attr = self.faft_client.cgpt.get_attributes()
2022*9c5db199SXin Li
2023*9c5db199SXin Li    def restore_cgpt_attributes(self):
2024*9c5db199SXin Li        """Restore CGPT partition table attributes."""
2025*9c5db199SXin Li        current_table = self.faft_client.cgpt.get_attributes()
2026*9c5db199SXin Li        if current_table == self._backup_cgpt_attr:
2027*9c5db199SXin Li            return
2028*9c5db199SXin Li        logging.info('CGPT table is changed. Original: %r. Current: %r.',
2029*9c5db199SXin Li                     self._backup_cgpt_attr,
2030*9c5db199SXin Li                     current_table)
2031*9c5db199SXin Li        self.faft_client.cgpt.set_attributes(
2032*9c5db199SXin Li                self._backup_cgpt_attr['A'], self._backup_cgpt_attr['B'])
2033*9c5db199SXin Li
2034*9c5db199SXin Li        self.switcher.mode_aware_reboot()
2035*9c5db199SXin Li        logging.info('Successfully restored CGPT table.')
2036*9c5db199SXin Li
2037*9c5db199SXin Li    def try_fwb(self, count=0):
2038*9c5db199SXin Li        """set to try booting FWB count # times
2039*9c5db199SXin Li
2040*9c5db199SXin Li        Wrapper to set fwb_tries for vboot1 and fw_try_count,fw_try_next for
2041*9c5db199SXin Li        vboot2
2042*9c5db199SXin Li
2043*9c5db199SXin Li        @param count: an integer specifying value to program into
2044*9c5db199SXin Li                      fwb_tries(vb1)/fw_try_next(vb2)
2045*9c5db199SXin Li        """
2046*9c5db199SXin Li        if self.fw_vboot2:
2047*9c5db199SXin Li            self.faft_client.system.set_fw_try_next('B', count)
2048*9c5db199SXin Li        else:
2049*9c5db199SXin Li            # vboot1: we need to boot into fwb at least once
2050*9c5db199SXin Li            if not count:
2051*9c5db199SXin Li                count = count + 1
2052*9c5db199SXin Li            self.faft_client.system.set_try_fw_b(count)
2053*9c5db199SXin Li
2054*9c5db199SXin Li    def identify_shellball(self, include_ec=None):
2055*9c5db199SXin Li        """Get the FWIDs of all targets and sections in the shellball
2056*9c5db199SXin Li
2057*9c5db199SXin Li        @param include_ec: if True, get EC fwids.
2058*9c5db199SXin Li                           If None (default), assume True if board has an EC
2059*9c5db199SXin Li        @return: the dict of versions in the shellball
2060*9c5db199SXin Li        """
2061*9c5db199SXin Li        fwids = dict()
2062*9c5db199SXin Li        fwids['bios'] = self.faft_client.updater.get_image_fwids('bios')
2063*9c5db199SXin Li
2064*9c5db199SXin Li        if include_ec is None:
2065*9c5db199SXin Li            if self.faft_config.platform.lower() == 'samus':
2066*9c5db199SXin Li                include_ec = False  # no ec.bin in shellball
2067*9c5db199SXin Li            else:
2068*9c5db199SXin Li                include_ec = self.faft_config.chrome_ec
2069*9c5db199SXin Li
2070*9c5db199SXin Li        if include_ec:
2071*9c5db199SXin Li            fwids['ec'] = self.faft_client.updater.get_image_fwids('ec')
2072*9c5db199SXin Li        return fwids
2073*9c5db199SXin Li
2074*9c5db199SXin Li    def modify_shellball(self, append, modify_ro=True, modify_ec=False):
2075*9c5db199SXin Li        """Modify the FWIDs of targets and sections in the shellball
2076*9c5db199SXin Li
2077*9c5db199SXin Li        @return: the full path of the shellball
2078*9c5db199SXin Li        """
2079*9c5db199SXin Li
2080*9c5db199SXin Li        if modify_ro:
2081*9c5db199SXin Li            self.faft_client.updater.modify_image_fwids(
2082*9c5db199SXin Li                    'bios', ['ro', 'a', 'b'])
2083*9c5db199SXin Li        else:
2084*9c5db199SXin Li            self.faft_client.updater.modify_image_fwids(
2085*9c5db199SXin Li                    'bios', ['a', 'b'])
2086*9c5db199SXin Li
2087*9c5db199SXin Li        if modify_ec:
2088*9c5db199SXin Li            if modify_ro:
2089*9c5db199SXin Li                self.faft_client.updater.modify_image_fwids(
2090*9c5db199SXin Li                        'ec', ['ro', 'rw'])
2091*9c5db199SXin Li            else:
2092*9c5db199SXin Li                self.faft_client.updater.modify_image_fwids(
2093*9c5db199SXin Li                        'ec', ['rw'])
2094*9c5db199SXin Li
2095*9c5db199SXin Li        modded_shellball = self.faft_client.updater.repack_shellball(append)
2096*9c5db199SXin Li
2097*9c5db199SXin Li        return modded_shellball
2098*9c5db199SXin Li
2099*9c5db199SXin Li    @staticmethod
2100*9c5db199SXin Li    def check_fwids_written(before_fwids, image_fwids, after_fwids,
2101*9c5db199SXin Li                            expected_written):
2102*9c5db199SXin Li        """Check the dicts of fwids for correctness after an update is applied.
2103*9c5db199SXin Li
2104*9c5db199SXin Li        The targets checked come from the keys of expected_written.
2105*9c5db199SXin Li        The sections checked come from the inner dicts of the fwids parameters.
2106*9c5db199SXin Li
2107*9c5db199SXin Li        The fwids should be keyed by target (flash type), then by section:
2108*9c5db199SXin Li        {'bios': {'ro': '<fwid>', 'a': '<fwid>', 'b': '<fwid>'},
2109*9c5db199SXin Li         'ec': {'ro': '<fwid>', 'rw': '<fwid>'}
2110*9c5db199SXin Li
2111*9c5db199SXin Li        For expected_written, the dict should be keyed by flash type only:
2112*9c5db199SXin Li        {'bios': ['ro'], 'ec': ['ro', 'rw']}
2113*9c5db199SXin Li        To expect the contents completely unchanged, give only the keys:
2114*9c5db199SXin Li        {'bios': [], 'ec': []} or {'bios': None, 'ec': None}
2115*9c5db199SXin Li
2116*9c5db199SXin Li        @param before_fwids: dict of versions from before the update
2117*9c5db199SXin Li        @param image_fwids: dict of versions in the update
2118*9c5db199SXin Li        @param after_fwids: dict of actual versions after the update
2119*9c5db199SXin Li        @param expected_written: dict indicating which ones should have changed
2120*9c5db199SXin Li        @return: list of error lines for mismatches
2121*9c5db199SXin Li
2122*9c5db199SXin Li        @type before_fwids: dict
2123*9c5db199SXin Li        @type image_fwids: dict | None
2124*9c5db199SXin Li        @type after_fwids: dict
2125*9c5db199SXin Li        @type expected_written: dict
2126*9c5db199SXin Li        @rtype: list
2127*9c5db199SXin Li        """
2128*9c5db199SXin Li        errors = []
2129*9c5db199SXin Li
2130*9c5db199SXin Li        if image_fwids is None:
2131*9c5db199SXin Li            image_fwids = {}
2132*9c5db199SXin Li
2133*9c5db199SXin Li        for target in sorted(expected_written.keys()):
2134*9c5db199SXin Li            # target is BIOS or EC
2135*9c5db199SXin Li
2136*9c5db199SXin Li            before_missing = (target not in before_fwids)
2137*9c5db199SXin Li            after_missing = (target not in after_fwids)
2138*9c5db199SXin Li            if before_missing or after_missing:
2139*9c5db199SXin Li                if before_missing:
2140*9c5db199SXin Li                    errors.append("...no before_fwids[%s]" % target)
2141*9c5db199SXin Li                if after_missing:
2142*9c5db199SXin Li                    errors.append("...no after_fwids[%s]" % target)
2143*9c5db199SXin Li                continue
2144*9c5db199SXin Li
2145*9c5db199SXin Li            written_sections = expected_written.get(target) or list()
2146*9c5db199SXin Li            written_sections = set(written_sections)
2147*9c5db199SXin Li
2148*9c5db199SXin Li            before_sections = set(before_fwids.get(target) or dict())
2149*9c5db199SXin Li            image_sections = set(image_fwids.get(target) or dict())
2150*9c5db199SXin Li            after_sections = set(after_fwids.get(target) or dict())
2151*9c5db199SXin Li
2152*9c5db199SXin Li            for section in before_sections | image_sections | after_sections:
2153*9c5db199SXin Li                # section is RO, RW, A, or B
2154*9c5db199SXin Li
2155*9c5db199SXin Li                before_fwid = before_fwids[target][section]
2156*9c5db199SXin Li                image_fwid = image_fwids.get(target, {}).get(section, None)
2157*9c5db199SXin Li                actual_fwid = after_fwids[target][section]
2158*9c5db199SXin Li
2159*9c5db199SXin Li                if section in written_sections:
2160*9c5db199SXin Li                    expected_fwid = image_fwid
2161*9c5db199SXin Li                    expected_desc = 'rewritten fwid (%s)' % expected_fwid
2162*9c5db199SXin Li                    if image_fwid == before_fwid:
2163*9c5db199SXin Li                        expected_desc = ('rewritten (no changes) fwid (%s)' %
2164*9c5db199SXin Li                                         expected_fwid)
2165*9c5db199SXin Li                else:
2166*9c5db199SXin Li                    expected_fwid = before_fwid
2167*9c5db199SXin Li                    expected_desc = 'original fwid (%s)' % expected_fwid
2168*9c5db199SXin Li
2169*9c5db199SXin Li                if actual_fwid == expected_fwid:
2170*9c5db199SXin Li                    actual_desc = 'correct value'
2171*9c5db199SXin Li
2172*9c5db199SXin Li                elif actual_fwid == image_fwid:
2173*9c5db199SXin Li                    actual_desc = 'rewritten fwid (%s)' % actual_fwid
2174*9c5db199SXin Li                    if image_fwid == before_fwid:
2175*9c5db199SXin Li                        # The flash could have been rewritten with the same fwid
2176*9c5db199SXin Li                        actual_desc = 'possibly written fwid (%s)' % actual_fwid
2177*9c5db199SXin Li
2178*9c5db199SXin Li                elif actual_fwid == before_fwid:
2179*9c5db199SXin Li                    actual_desc = 'original fwid (%s)' % actual_fwid
2180*9c5db199SXin Li
2181*9c5db199SXin Li                else:
2182*9c5db199SXin Li                    actual_desc = 'unknown fwid (%s)' % actual_fwid
2183*9c5db199SXin Li
2184*9c5db199SXin Li                msg = ("...FWID (%s %s): expected %s, got %s" %
2185*9c5db199SXin Li                       (target.upper(), section.upper(),
2186*9c5db199SXin Li                        expected_desc, actual_desc))
2187*9c5db199SXin Li
2188*9c5db199SXin Li                if actual_fwid != expected_fwid:
2189*9c5db199SXin Li                    errors.append(msg)
2190*9c5db199SXin Li        return errors
2191*9c5db199SXin Li
2192*9c5db199SXin Li
2193*9c5db199SXin Li    def fwmp_is_cleared(self):
2194*9c5db199SXin Li        """Return True if the FWMP has been created"""
2195*9c5db199SXin Li        res = self.host.run('cryptohome '
2196*9c5db199SXin Li                            '--action=get_firmware_management_parameters',
2197*9c5db199SXin Li                            ignore_status=True)
2198*9c5db199SXin Li        if res.exit_status and res.exit_status != self.FWMP_CLEARED_EXIT_STATUS:
2199*9c5db199SXin Li            raise error.TestError('Could not run cryptohome command %r' % res)
2200*9c5db199SXin Li        return (self.FWMP_CLEARED_ERROR_MSG in res.stdout
2201*9c5db199SXin Li                or tpm_utils.FwmpIsAllZero(res.stdout))
2202*9c5db199SXin Li
2203*9c5db199SXin Li
2204*9c5db199SXin Li    def _tpm_is_owned(self):
2205*9c5db199SXin Li        """Returns True if the tpm is owned"""
2206*9c5db199SXin Li        result = self.host.run('tpm_manager_client status --nonsensitive',
2207*9c5db199SXin Li                               ignore_status=True)
2208*9c5db199SXin Li        logging.debug(result)
2209*9c5db199SXin Li        return result.exit_status == 0 and 'is_owned: true' in result.stdout
2210*9c5db199SXin Li
2211*9c5db199SXin Li    def clear_fwmp(self):
2212*9c5db199SXin Li        """Clear the FWMP"""
2213*9c5db199SXin Li        if self.fwmp_is_cleared():
2214*9c5db199SXin Li            return
2215*9c5db199SXin Li        tpm_utils.ClearTPMOwnerRequest(self.host, wait_for_ready=True)
2216*9c5db199SXin Li        self.host.run('tpm_manager_client take_ownership')
2217*9c5db199SXin Li        if not utils.wait_for_value(self._tpm_is_owned, expected_value=True):
2218*9c5db199SXin Li            raise error.TestError('Unable to own tpm while clearing fwmp.')
2219*9c5db199SXin Li        self.host.run('cryptohome '
2220*9c5db199SXin Li                      '--action=remove_firmware_management_parameters')
2221*9c5db199SXin Li
2222*9c5db199SXin Li    def wait_for(self, cfg_field, action_msg=None, extra_time=0):
2223*9c5db199SXin Li        """Waits for time specified in a config.
2224*9c5db199SXin Li
2225*9c5db199SXin Li        @ivar cfg_field: The name of the config field that specifies the
2226*9c5db199SXin Li                            time to wait.
2227*9c5db199SXin Li        @ivar action_msg: Optional log message describing the action that
2228*9c5db199SXin Li                            will occur after the wait.
2229*9c5db199SXin Li        @ivar extra_time: Additional time to be added to time from config.
2230*9c5db199SXin Li        """
2231*9c5db199SXin Li        wait_time = self.faft_config.__getattr__(cfg_field) + extra_time
2232*9c5db199SXin Li        if extra_time:
2233*9c5db199SXin Li            wait_src = "%s + %s" % (cfg_field, extra_time)
2234*9c5db199SXin Li        else:
2235*9c5db199SXin Li            wait_src = cfg_field
2236*9c5db199SXin Li
2237*9c5db199SXin Li        units = 'second' if wait_time==1 else 'seconds'
2238*9c5db199SXin Li        start_msg = "Waiting %s(%s) %s" % (wait_time, wait_src, units)
2239*9c5db199SXin Li        if action_msg:
2240*9c5db199SXin Li            start_msg += ", before '%s'" % action_msg
2241*9c5db199SXin Li        start_msg += "."
2242*9c5db199SXin Li
2243*9c5db199SXin Li        logging.info(start_msg)
2244*9c5db199SXin Li        time.sleep(wait_time)
2245*9c5db199SXin Li        logging.info("Done waiting.")
2246*9c5db199SXin Li
2247*9c5db199SXin Li    def _try_to_bring_dut_up(self):
2248*9c5db199SXin Li        """Try to quickly get the dut in a pingable state"""
2249*9c5db199SXin Li        if not hasattr(self, 'cr50'):
2250*9c5db199SXin Li            raise error.TestNAError('Test can only be run on devices with '
2251*9c5db199SXin Li                                    'access to the Cr50 console')
2252*9c5db199SXin Li        logging.info('Bringing DUT up')
2253*9c5db199SXin Li
2254*9c5db199SXin Li        self.servo.set_nocheck('cold_reset', 'off')
2255*9c5db199SXin Li        self.servo.set_nocheck('warm_reset', 'off')
2256*9c5db199SXin Li
2257*9c5db199SXin Li        time.sleep(self.cr50.SHORT_WAIT)
2258*9c5db199SXin Li        if not self.cr50.ap_is_on():
2259*9c5db199SXin Li            logging.info('Pressing power button to turn on AP')
2260*9c5db199SXin Li            self.servo.power_short_press()
2261*9c5db199SXin Li
2262*9c5db199SXin Li        end_time = time.time() + self.RESPONSE_TIMEOUT
2263*9c5db199SXin Li        while not self.host.ping_wait_up(
2264*9c5db199SXin Li                self.faft_config.delay_reboot_to_ping * 2):
2265*9c5db199SXin Li            if time.time() > end_time:
2266*9c5db199SXin Li                logging.warning(
2267*9c5db199SXin Li                        'DUT is unresponsive after trying to bring it up')
2268*9c5db199SXin Li                return
2269*9c5db199SXin Li            self.servo.get_power_state_controller().reset()
2270*9c5db199SXin Li            logging.info('DUT did not respond. Resetting it.')
2271*9c5db199SXin Li
2272*9c5db199SXin Li    def _check_open_and_press_power_button(self):
2273*9c5db199SXin Li        """Check stdout and press the power button if prompted.
2274*9c5db199SXin Li
2275*9c5db199SXin Li        Returns:
2276*9c5db199SXin Li            True if the process is still running.
2277*9c5db199SXin Li        """
2278*9c5db199SXin Li        if not hasattr(self, 'cr50'):
2279*9c5db199SXin Li            raise error.TestNAError('Test can only be run on devices with '
2280*9c5db199SXin Li                                    'access to the Cr50 console')
2281*9c5db199SXin Li
2282*9c5db199SXin Li        logging.info(self._get_ccd_open_output())
2283*9c5db199SXin Li        self.servo.power_short_press()
2284*9c5db199SXin Li        logging.info('long int power button press')
2285*9c5db199SXin Li        # power button press cr50 erases nvmem and resets the dut before setting
2286*9c5db199SXin Li        # the state to open. Wait a bit so we don't check the ccd state in the
2287*9c5db199SXin Li        # middle of this reset process. Power button requests happen once a
2288*9c5db199SXin Li        # minute, so waiting 10 seconds isn't a big deal.
2289*9c5db199SXin Li        time.sleep(10)
2290*9c5db199SXin Li        return (self.cr50.OPEN == self.cr50.get_ccd_level() or
2291*9c5db199SXin Li                self._ccd_open_job.sp.poll() is not None)
2292*9c5db199SXin Li
2293*9c5db199SXin Li    def _get_ccd_open_output(self):
2294*9c5db199SXin Li        """Read the new output."""
2295*9c5db199SXin Li        if not hasattr(self, 'cr50'):
2296*9c5db199SXin Li            raise error.TestNAError('Test can only be run on devices with '
2297*9c5db199SXin Li                                    'access to the Cr50 console')
2298*9c5db199SXin Li
2299*9c5db199SXin Li        self._ccd_open_job.process_output()
2300*9c5db199SXin Li        output = self._ccd_open_stdout.getvalue()
2301*9c5db199SXin Li        self._ccd_open_stdout.seek(self._ccd_open_last_len)
2302*9c5db199SXin Li        self._ccd_open_last_len = len(output)
2303*9c5db199SXin Li        return self._ccd_open_stdout.read().strip()
2304*9c5db199SXin Li
2305*9c5db199SXin Li    def _close_ccd_open_job(self):
2306*9c5db199SXin Li        """Terminate the process and check the results."""
2307*9c5db199SXin Li        if not hasattr(self, 'cr50'):
2308*9c5db199SXin Li            raise error.TestNAError('Test can only be run on devices with '
2309*9c5db199SXin Li                                    'access to the Cr50 console')
2310*9c5db199SXin Li
2311*9c5db199SXin Li        exit_status = utils.nuke_subprocess(self._ccd_open_job.sp)
2312*9c5db199SXin Li        stdout = self._ccd_open_stdout.getvalue().strip()
2313*9c5db199SXin Li        delattr(self, '_ccd_open_job')
2314*9c5db199SXin Li        if stdout:
2315*9c5db199SXin Li            logging.info('stdout of ccd open:\n%s', stdout)
2316*9c5db199SXin Li        if exit_status:
2317*9c5db199SXin Li            logging.info('exit status: %d', exit_status)
2318*9c5db199SXin Li        if 'Error' in stdout:
2319*9c5db199SXin Li            raise error.TestFail('ccd open Error %s' %
2320*9c5db199SXin Li                                 stdout.split('Error')[-1])
2321*9c5db199SXin Li        if self.cr50.OPEN != self.cr50.get_ccd_level():
2322*9c5db199SXin Li            raise error.TestFail('unable to open cr50: %s' % stdout)
2323*9c5db199SXin Li        else:
2324*9c5db199SXin Li            logging.info('Opened Cr50')
2325*9c5db199SXin Li
2326*9c5db199SXin Li    def ccd_open_from_ap(self):
2327*9c5db199SXin Li        """Start the open process and press the power button."""
2328*9c5db199SXin Li        if not hasattr(self, 'cr50'):
2329*9c5db199SXin Li            raise error.TestNAError('Test can only be run on devices with '
2330*9c5db199SXin Li                                    'access to the Cr50 console')
2331*9c5db199SXin Li
2332*9c5db199SXin Li        # Opening CCD requires power button presses. If those presses would
2333*9c5db199SXin Li        # power off the AP and prevent CCD open from completing, ignore them.
2334*9c5db199SXin Li        if self.faft_config.ec_forwards_short_pp_press:
2335*9c5db199SXin Li            self.stop_powerd()
2336*9c5db199SXin Li
2337*9c5db199SXin Li        # Make sure the test waits long enough to avoid ccd rate limiting.
2338*9c5db199SXin Li        time.sleep(self.cr50.CCD_PASSWORD_RATE_LIMIT)
2339*9c5db199SXin Li
2340*9c5db199SXin Li        self._ccd_open_last_len = 0
2341*9c5db199SXin Li
2342*9c5db199SXin Li        self._ccd_open_stdout = six.StringIO()
2343*9c5db199SXin Li
2344*9c5db199SXin Li        ccd_open_cmd = utils.sh_escape('gsctool -a -o')
2345*9c5db199SXin Li        full_ssh_cmd = '%s "%s"' % (self.host.ssh_command(options='-tt'),
2346*9c5db199SXin Li                                    ccd_open_cmd)
2347*9c5db199SXin Li        # Start running the Cr50 Open process in the background.
2348*9c5db199SXin Li        self._ccd_open_job = utils.BgJob(full_ssh_cmd,
2349*9c5db199SXin Li                                         nickname='ccd_open',
2350*9c5db199SXin Li                                         stdout_tee=self._ccd_open_stdout,
2351*9c5db199SXin Li                                         stderr_tee=utils.TEE_TO_LOGS)
2352*9c5db199SXin Li        if self._ccd_open_job == None:
2353*9c5db199SXin Li            raise error.TestFail('could not start ccd open')
2354*9c5db199SXin Li
2355*9c5db199SXin Li        try:
2356*9c5db199SXin Li            # Wait for the first gsctool power button prompt before starting the
2357*9c5db199SXin Li            # open process.
2358*9c5db199SXin Li            logging.info(self._get_ccd_open_output())
2359*9c5db199SXin Li            # Cr50 starts out by requesting 5 quick presses then 4 longer
2360*9c5db199SXin Li            # power button presses. Run the quick presses without looking at the
2361*9c5db199SXin Li            # command output, because getting the output can take some time. For
2362*9c5db199SXin Li            # the presses that require a 1 minute wait check the output between
2363*9c5db199SXin Li            # presses, so we can catch errors
2364*9c5db199SXin Li            #
2365*9c5db199SXin Li            # run quick presses for 30 seconds. It may take a couple of seconds
2366*9c5db199SXin Li            # for open to start. 10 seconds should be enough. 30 is just used
2367*9c5db199SXin Li            # because it will definitely be enough, and this process takes 300
2368*9c5db199SXin Li            # seconds, so doing quick presses for 30 seconds won't matter.
2369*9c5db199SXin Li            end_time = time.time() + 30
2370*9c5db199SXin Li            while time.time() < end_time:
2371*9c5db199SXin Li                self.servo.power_short_press()
2372*9c5db199SXin Li                logging.info('short int power button press')
2373*9c5db199SXin Li                time.sleep(self.PP_SHORT_INTERVAL)
2374*9c5db199SXin Li            # Poll the output and press the power button for the longer presses.
2375*9c5db199SXin Li            utils.wait_for_value(self._check_open_and_press_power_button,
2376*9c5db199SXin Li                                 expected_value=True,
2377*9c5db199SXin Li                                 timeout_sec=self.cr50.PP_LONG)
2378*9c5db199SXin Li        except Exception as e:
2379*9c5db199SXin Li            logging.info(e)
2380*9c5db199SXin Li            raise
2381*9c5db199SXin Li        finally:
2382*9c5db199SXin Li            self._close_ccd_open_job()
2383*9c5db199SXin Li            self._try_to_bring_dut_up()
2384*9c5db199SXin Li        logging.info(self.cr50.get_ccd_info())
2385*9c5db199SXin Li
2386*9c5db199SXin Li    def enter_mode_after_checking_cr50_state(self, mode):
2387*9c5db199SXin Li        """Reboot to mode if cr50 doesn't already match the state"""
2388*9c5db199SXin Li        if not hasattr(self, 'cr50'):
2389*9c5db199SXin Li            raise error.TestNAError('Test can only be run on devices with '
2390*9c5db199SXin Li                                    'access to the Cr50 console')
2391*9c5db199SXin Li
2392*9c5db199SXin Li        # If the device is already in the correct mode, don't do anything
2393*9c5db199SXin Li        if (mode == 'dev') == self.cr50.in_dev_mode():
2394*9c5db199SXin Li            logging.info('already in %r mode', mode)
2395*9c5db199SXin Li            return
2396*9c5db199SXin Li
2397*9c5db199SXin Li        self.switcher.reboot_to_mode(to_mode=mode)
2398*9c5db199SXin Li
2399*9c5db199SXin Li        if (mode == 'dev') != self.cr50.in_dev_mode():
2400*9c5db199SXin Li            raise error.TestError('Unable to enter %r mode' % mode)
2401*9c5db199SXin Li
2402*9c5db199SXin Li    def fast_ccd_open(self, enable_testlab=False, reset_ccd=True,
2403*9c5db199SXin Li                      dev_mode=False):
2404*9c5db199SXin Li        """Try to use ccd testlab open. If that fails, do regular ap open.
2405*9c5db199SXin Li
2406*9c5db199SXin Li        Args:
2407*9c5db199SXin Li            enable_testlab: If True, enable testlab mode after cr50 is open.
2408*9c5db199SXin Li            reset_ccd: If True, reset ccd after open.
2409*9c5db199SXin Li            dev_mode: True if the device should be in dev mode after ccd is
2410*9c5db199SXin Li                      is opened.
2411*9c5db199SXin Li        """
2412*9c5db199SXin Li        if not hasattr(self, 'cr50'):
2413*9c5db199SXin Li            raise error.TestNAError('Test can only be run on devices with '
2414*9c5db199SXin Li                                    'access to the Cr50 console')
2415*9c5db199SXin Li
2416*9c5db199SXin Li        if self.servo.main_device_is_ccd() and not self.cr50.testlab_is_on():
2417*9c5db199SXin Li            error_txt = 'because the main servo device is CCD.'
2418*9c5db199SXin Li            if enable_testlab:
2419*9c5db199SXin Li                raise error.TestNAError('Cannot enable testlab: %s' % error_txt)
2420*9c5db199SXin Li            elif reset_ccd:
2421*9c5db199SXin Li                raise error.TestNAError('CCD reset not allowed: %s' % error_txt)
2422*9c5db199SXin Li
2423*9c5db199SXin Li        if not self.faft_config.has_powerbutton:
2424*9c5db199SXin Li            logging.warning('No power button', exc_info=True)
2425*9c5db199SXin Li            enable_testlab = False
2426*9c5db199SXin Li
2427*9c5db199SXin Li        # Try to use testlab open first, so we don't have to wait for the
2428*9c5db199SXin Li        # physical presence check.
2429*9c5db199SXin Li        self.cr50.send_command('ccd testlab open')
2430*9c5db199SXin Li        if self.cr50.OPEN != self.cr50.get_ccd_level():
2431*9c5db199SXin Li            if self.servo.has_control('chassis_open'):
2432*9c5db199SXin Li                self.servo.set('chassis_open', 'yes')
2433*9c5db199SXin Li            pw = '' if self.cr50.password_is_reset() else self.CCD_PASSWORD
2434*9c5db199SXin Li            # Use the console to open cr50 without entering dev mode if
2435*9c5db199SXin Li            # possible. Ittakes longer and relies on more systems to enter dev
2436*9c5db199SXin Li            # mode and ssh into the AP. Skip the steps that aren't required.
2437*9c5db199SXin Li            if not (pw or self.cr50.get_cap(
2438*9c5db199SXin Li                            'OpenNoDevMode')[self.cr50.CAP_IS_ACCESSIBLE]):
2439*9c5db199SXin Li                self.enter_mode_after_checking_cr50_state('dev')
2440*9c5db199SXin Li
2441*9c5db199SXin Li            if pw or self.cr50.get_cap(
2442*9c5db199SXin Li                            'OpenFromUSB')[self.cr50.CAP_IS_ACCESSIBLE]:
2443*9c5db199SXin Li                self.cr50.set_ccd_level(self.cr50.OPEN, pw)
2444*9c5db199SXin Li            else:
2445*9c5db199SXin Li                self.ccd_open_from_ap()
2446*9c5db199SXin Li
2447*9c5db199SXin Li            if self.servo.has_control('chassis_open'):
2448*9c5db199SXin Li                self.servo.set('chassis_open', 'no')
2449*9c5db199SXin Li
2450*9c5db199SXin Li            if enable_testlab:
2451*9c5db199SXin Li                self.cr50.set_ccd_testlab('on')
2452*9c5db199SXin Li
2453*9c5db199SXin Li        if reset_ccd:
2454*9c5db199SXin Li            self.cr50.ccd_reset()
2455*9c5db199SXin Li
2456*9c5db199SXin Li        # In default, the device should be in normal mode. After opening cr50,
2457*9c5db199SXin Li        # the TPM should be cleared and the device should automatically reset to
2458*9c5db199SXin Li        # normal mode. However, some tests might want the device in 'dev' mode.
2459*9c5db199SXin Li        self.enter_mode_after_checking_cr50_state('dev' if dev_mode else
2460*9c5db199SXin Li                                                 'normal')
2461