1# Copyright 2019 The Chromium OS Authors. All rights reserved.
2# Use of this source code is governed by a BSD-style license that can be
3# found in the LICENSE file.
4
5import logging
6import time
7import os
8
9from autotest_lib.client.common_lib import error
10from autotest_lib.server.cros.faft.firmware_test import FirmwareTest
11
12
13class firmware_WilcoDiagnosticsMode(FirmwareTest):
14    """Corrupt the Wilco diagnostics image and then reinstall it.
15
16    Wilco supports entry into a diagnostics image from recovery mode. The image
17    is stored in the RW_LEGACY firmware section and updated during AP firmware
18    updates.  Entry into the image should fail if the image is corrupted.
19    Updating the firmware should restore the diagnostics image.
20    """
21    version = 1
22
23    # The delay between pressing <F12> to enter diagnostics mode and reaching
24    # the confirmation screen; typically about 10 seconds; overshoot to be safe.
25    DIAGNOSTICS_CONFIRM_SCREEN_DELAY_SECONDS = 20
26    # The delay between pressing <Power> to confirm entry to diagnostics mode
27    # and rebooting into diagnostics mode.
28    DIAGNOSTICS_CONFIRM_REBOOT_DELAY_SECONDS = 8
29    # The delay between rebooting to enter diagnostics mode and rebooting again
30    # if that fails.
31    DIAGNOSTICS_FAIL_REBOOT_DELAY_SECONDS = 8
32    # The name of the diagnostics image file in CBFS.
33    DIAG_CBFS_NAME = 'altfw/diag'
34
35    def initialize(self, host, cmdline_args):
36        super(firmware_WilcoDiagnosticsMode, self).initialize(
37                host, cmdline_args)
38
39        if not self.faft_config.has_diagnostics_image:
40            raise error.TestNAError('No diagnostics image for this board.')
41
42        self.setup_firmwareupdate_shellball(shellball=None)
43        # Make sure that the shellball is retained over subsequent power cycles.
44        self.blocking_sync()
45        self.switcher.setup_mode('normal')
46
47    def cleanup(self):
48        self._client.reset_via_servo()
49
50        super(firmware_WilcoDiagnosticsMode, self).cleanup()
51
52    def _corrupt_diagnostics_image(self):
53        # Extract the diagnostics image from the firmware image, corrupt the
54        # image, and write a new firmware image with that corrupt diagnostics
55        # image.
56        local_filename = 'diag.bin'
57        cbfs_work_dir = self.faft_client.updater.cbfs_setup_work_dir()
58        bios_cbfs_path = os.path.join(cbfs_work_dir,
59                self.faft_client.updater.get_bios_relative_path())
60        diag_cbfs_path = os.path.join(cbfs_work_dir, local_filename)
61
62        logging.info('Extracting diagnostics')
63        self.faft_client.updater.cbfs_extract_diagnostics(self.DIAG_CBFS_NAME,
64                diag_cbfs_path)
65
66        logging.info('Corrupting diagnostics')
67        self.faft_client.updater.corrupt_diagnostics_image(diag_cbfs_path)
68
69        logging.info('Replacing diagnostics')
70        self.faft_client.updater.cbfs_replace_diagnostics(self.DIAG_CBFS_NAME,
71                diag_cbfs_path)
72
73        logging.info('Writing back BIOS')
74        self.faft_client.bios.write_whole(bios_cbfs_path)
75        self.switcher.mode_aware_reboot()
76
77    def _press_f12(self):
78        self.servo.set_nocheck('arb_key_config', '<f12>')
79        self.servo.set_nocheck('arb_key', 'tab')
80
81    def _enter_diagnostics_mode(self):
82        # Reboot to the recovery screen, press <F12>, and press power to
83        # confirm.
84        self.servo.switch_usbkey('host')
85        psc = self.servo.get_power_state_controller()
86        logging.info('Powering off')
87        if self.cr50.ap_is_on():
88            self.servo.power_key(self.faft_config.hold_pwr_button_poweroff)
89            logging.info('Waiting for power off')
90            time.sleep(1)
91            self._client.close_main_ssh()
92        logging.info('Booting to recovery screen')
93        psc.power_on(psc.REC_ON)
94
95        logging.info('Sleeping %s seconds (firmware_screen)',
96                     self.faft_config.firmware_screen)
97        time.sleep(self.faft_config.firmware_screen)
98        if not self.cr50.ap_is_on():
99            raise error.TestFail('Expected AP on when booting to recovery')
100        logging.info('Pressing <F12>')
101        self._press_f12()
102        logging.info(
103                'Sleeping %s seconds (DIAGNOSTICS_CONFIRM_SCREEN_DELAY_SECONDS)',
104                self.DIAGNOSTICS_CONFIRM_SCREEN_DELAY_SECONDS)
105        time.sleep(self.DIAGNOSTICS_CONFIRM_SCREEN_DELAY_SECONDS)
106        logging.info('Pressing <Power> to confirm')
107        self.servo.power_short_press()
108        # At this point, the DUT will try to reboot into diagnostics mode.
109
110    def _verify_diagnostics_mode(self):
111        """Checks that the AP is on and ssh fails.
112
113        This is not certain that we are in the diagnostic mode, but gives some
114        confidence.
115        """
116        # Wait long enough that DUT would have rebooted to normal mode if
117        # diagnostics mode failed.
118        logging.info(
119                'Sleeping %s seconds (DIAGNOSTICS_FAIL_REBOOT_DELAY_SECONDS)',
120                self.DIAGNOSTICS_FAIL_REBOOT_DELAY_SECONDS)
121        time.sleep(self.DIAGNOSTICS_FAIL_REBOOT_DELAY_SECONDS)
122        if not self.cr50.ap_is_on():
123            raise error.TestFail(
124                    'AP is off, expected diagnostics mode. Is diagnostic '
125                    'corrupted? Run chromeos-firmwareupdate --mode=recovery')
126        logging.info('Sleeping %s seconds (delay_reboot_to_ping)',
127                     self.faft_config.delay_reboot_to_ping)
128        time.sleep(self.faft_config.delay_reboot_to_ping)
129        self.switcher.wait_for_client_offline(timeout=5)
130        logging.info('DUT offline after entering diagnostics mode')
131
132    def run_once(self):
133        """Run the body of the test."""
134        logging.info('Attempting to enter diagnostics mode')
135        self._enter_diagnostics_mode()
136        self._verify_diagnostics_mode()
137        self._client.reset_via_servo()
138        self.switcher.wait_for_client()
139
140        # Corrupt the diagnostics image, try to reboot into diagnostics mode,
141        # and verify that the DUT ends up in normal mode (indicating failure to
142        # enter diagnostics mode).
143        self._corrupt_diagnostics_image()
144        self._enter_diagnostics_mode()
145        logging.info(
146                'Sleeping %s seconds (DIAGNOSTICS_FAIL_REBOOT_DELAY_SECONDS)',
147                self.DIAGNOSTICS_FAIL_REBOOT_DELAY_SECONDS)
148        time.sleep(self.DIAGNOSTICS_FAIL_REBOOT_DELAY_SECONDS)
149        # If the diagnostic mode fails, it might just power off
150        if not self.cr50.ap_is_on():
151            logging.info('AP off, pressing <Power> to boot to normal mode')
152            self.servo.power_short_press()
153        self.switcher.wait_for_client()
154        self.check_state((self.checkers.mode_checker, 'normal'))
155
156        # Update the firmware to restore the diagnostics image, reboot into
157        # diagnostics mode, and verify that the DUT goes down (indicating
158        # success).
159        logging.info('Updating firmware')
160        self.faft_client.updater.run_autoupdate(None)
161        logging.info('Rebooting to apply firmware update')
162        self.switcher.mode_aware_reboot()
163
164        logging.info('Attempting to enter diagnostics mode')
165        self._enter_diagnostics_mode()
166        self._verify_diagnostics_mode()
167