xref: /aosp_15_r20/external/autotest/server/cros/faft/fingerprint_test.py (revision 9c5db1993ded3edbeafc8092d69fe5de2ee02df7)
1*9c5db199SXin Li# Lint as: python2, python3
2*9c5db199SXin Li# Copyright 2018 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 Liimport logging
7*9c5db199SXin Liimport os
8*9c5db199SXin Liimport time
9*9c5db199SXin Li
10*9c5db199SXin Liimport six
11*9c5db199SXin Li
12*9c5db199SXin Lifrom autotest_lib.server import test
13*9c5db199SXin Lifrom autotest_lib.server.cros import filesystem_util
14*9c5db199SXin Lifrom autotest_lib.client.common_lib import error, utils
15*9c5db199SXin Li
16*9c5db199SXin Li
17*9c5db199SXin Liclass FingerprintTest(test.test):
18*9c5db199SXin Li    """Base class that sets up helpers for fingerprint tests."""
19*9c5db199SXin Li    version = 1
20*9c5db199SXin Li
21*9c5db199SXin Li    # Location of firmware from the build on the DUT
22*9c5db199SXin Li    _FINGERPRINT_BUILD_FW_DIR = '/opt/google/biod/fw'
23*9c5db199SXin Li
24*9c5db199SXin Li    _DISABLE_FP_UPDATER_FILE = '.disable_fp_updater'
25*9c5db199SXin Li
26*9c5db199SXin Li    _UPSTART_DIR = '/etc/init'
27*9c5db199SXin Li    _BIOD_UPSTART_JOB_FILE = 'biod.conf'
28*9c5db199SXin Li    _STATEFUL_PARTITION_DIR = '/mnt/stateful_partition'
29*9c5db199SXin Li
30*9c5db199SXin Li    _GENIMAGES_SCRIPT_NAME = 'gen_test_images.sh'
31*9c5db199SXin Li    _GENIMAGES_OUTPUT_DIR_NAME = 'images'
32*9c5db199SXin Li
33*9c5db199SXin Li    _TEST_IMAGE_FORMAT_MAP = {
34*9c5db199SXin Li        'TEST_IMAGE_ORIGINAL': '%s.bin',
35*9c5db199SXin Li        'TEST_IMAGE_DEV': '%s.dev',
36*9c5db199SXin Li        'TEST_IMAGE_CORRUPT_FIRST_BYTE': '%s_corrupt_first_byte.bin',
37*9c5db199SXin Li        'TEST_IMAGE_CORRUPT_LAST_BYTE': '%s_corrupt_last_byte.bin',
38*9c5db199SXin Li        'TEST_IMAGE_DEV_RB_ZERO': '%s.dev.rb0',
39*9c5db199SXin Li        'TEST_IMAGE_DEV_RB_ONE': '%s.dev.rb1',
40*9c5db199SXin Li        'TEST_IMAGE_DEV_RB_NINE': '%s.dev.rb9'
41*9c5db199SXin Li    }
42*9c5db199SXin Li
43*9c5db199SXin Li    _ROLLBACK_ZERO_BLOCK_ID = '0'
44*9c5db199SXin Li    _ROLLBACK_INITIAL_BLOCK_ID = '1'
45*9c5db199SXin Li    _ROLLBACK_INITIAL_MIN_VERSION = '0'
46*9c5db199SXin Li    _ROLLBACK_INITIAL_RW_VERSION = '0'
47*9c5db199SXin Li
48*9c5db199SXin Li    _SERVER_GENERATED_FW_DIR_NAME = 'generated_fw'
49*9c5db199SXin Li
50*9c5db199SXin Li    _DUT_TMP_PATH_BASE = '/tmp/fp_test'
51*9c5db199SXin Li
52*9c5db199SXin Li    # Name of key in "futility show" output corresponds to the signing key ID
53*9c5db199SXin Li    _FUTILITY_KEY_ID_KEY_NAME = 'ID'
54*9c5db199SXin Li
55*9c5db199SXin Li    # Types of firmware
56*9c5db199SXin Li    _FIRMWARE_TYPE_RO = 'RO'
57*9c5db199SXin Li    _FIRMWARE_TYPE_RW = 'RW'
58*9c5db199SXin Li
59*9c5db199SXin Li    # Types of signing keys
60*9c5db199SXin Li    _KEY_TYPE_DEV = 'dev'
61*9c5db199SXin Li    _KEY_TYPE_PRE_MP = 'premp'
62*9c5db199SXin Li    _KEY_TYPE_MP = 'mp'
63*9c5db199SXin Li
64*9c5db199SXin Li    # EC board names for FPMCUs
65*9c5db199SXin Li    _FP_BOARD_NAME_BLOONCHIPPER = 'bloonchipper'
66*9c5db199SXin Li    _FP_BOARD_NAME_DARTMONKEY = 'dartmonkey'
67*9c5db199SXin Li    _FP_BOARD_NAME_NOCTURNE = 'nocturne_fp'
68*9c5db199SXin Li    _FP_BOARD_NAME_NAMI = 'nami_fp'
69*9c5db199SXin Li
70*9c5db199SXin Li    # Map from signing key ID to type of signing key
71*9c5db199SXin Li    _KEY_ID_MAP_ = {
72*9c5db199SXin Li        # bloonchipper
73*9c5db199SXin Li        '61382804da86b4156d666cc9a976088f8b647d44': _KEY_TYPE_DEV,
74*9c5db199SXin Li        '07b1af57220c196e363e68d73a5966047c77011e': _KEY_TYPE_PRE_MP,
75*9c5db199SXin Li        '1c590ef36399f6a2b2ef87079c135b69ef89eb60': _KEY_TYPE_MP,
76*9c5db199SXin Li
77*9c5db199SXin Li        # dartmonkey
78*9c5db199SXin Li        '257a0aa3ac9e81aa4bc3aabdb6d3d079117c5799': _KEY_TYPE_MP,
79*9c5db199SXin Li
80*9c5db199SXin Li        # nocturne
81*9c5db199SXin Li        '8a8fc039a9463271995392f079b83ce33832d07d': _KEY_TYPE_DEV,
82*9c5db199SXin Li        '6f38c866182bd9bf7a4462c06ac04fa6a0074351': _KEY_TYPE_MP,
83*9c5db199SXin Li        'f6f7d96c48bd154dbae7e3fe3a3b4c6268a10934': _KEY_TYPE_PRE_MP,
84*9c5db199SXin Li
85*9c5db199SXin Li        # nami
86*9c5db199SXin Li        '754aea623d69975a22998f7b97315dd53115d723': _KEY_TYPE_PRE_MP,
87*9c5db199SXin Li        '35486c0090ca390408f1fbbf2a182966084fe2f8': _KEY_TYPE_MP
88*9c5db199SXin Li
89*9c5db199SXin Li    }
90*9c5db199SXin Li
91*9c5db199SXin Li    # RO versions that are flashed in the factory
92*9c5db199SXin Li    # (for eternity for a given board)
93*9c5db199SXin Li    _GOLDEN_RO_FIRMWARE_VERSION_MAP = {
94*9c5db199SXin Li            _FP_BOARD_NAME_BLOONCHIPPER: {
95*9c5db199SXin Li                    'hatch': 'bloonchipper_v2.0.4277-9f652bb3',
96*9c5db199SXin Li                    'zork': 'bloonchipper_v2.0.5938-197506c1',
97*9c5db199SXin Li                    'volteer': 'bloonchipper_v2.0.5938-197506c1',
98*9c5db199SXin Li                    'brya': 'bloonchipper_v2.0.5938-197506c1',
99*9c5db199SXin Li                    'guybrush': 'bloonchipper_v2.0.5938-197506c1',
100*9c5db199SXin Li            },
101*9c5db199SXin Li            _FP_BOARD_NAME_DARTMONKEY: 'dartmonkey_v2.0.2887-311310808',
102*9c5db199SXin Li            _FP_BOARD_NAME_NOCTURNE: 'nocturne_fp_v2.2.64-58cf5974e',
103*9c5db199SXin Li            _FP_BOARD_NAME_NAMI: 'nami_fp_v2.2.144-7a08e07eb',
104*9c5db199SXin Li    }
105*9c5db199SXin Li
106*9c5db199SXin Li    _FIRMWARE_VERSION_SHA256SUM = 'sha256sum'
107*9c5db199SXin Li    _FIRMWARE_VERSION_RO_VERSION = 'ro_version'
108*9c5db199SXin Li    _FIRMWARE_VERSION_RW_VERSION = 'rw_version'
109*9c5db199SXin Li    _FIRMWARE_VERSION_KEY_ID = 'key_id'
110*9c5db199SXin Li
111*9c5db199SXin Li    # Map of attributes for a given board's various firmware file releases
112*9c5db199SXin Li    #
113*9c5db199SXin Li    # Two purposes:
114*9c5db199SXin Li    #   1) Documents the exact versions and keys used for a given firmware file.
115*9c5db199SXin Li    #   2) Used to verify that files that end up in the build (and therefore
116*9c5db199SXin Li    #      what we release) is exactly what we expect.
117*9c5db199SXin Li    _FIRMWARE_VERSION_MAP = {
118*9c5db199SXin Li        _FP_BOARD_NAME_BLOONCHIPPER: {
119*9c5db199SXin Li            'bloonchipper_v2.0.4277-9f652bb3-RO_v2.0.13589-727a419-RW.bin': {
120*9c5db199SXin Li                _FIRMWARE_VERSION_SHA256SUM: 'b500a08d1c4f49ac1455214f1957f178288a2f4b36b40e7cd49acad1d0896ccc',
121*9c5db199SXin Li                _FIRMWARE_VERSION_RO_VERSION: 'bloonchipper_v2.0.4277-9f652bb3',
122*9c5db199SXin Li                _FIRMWARE_VERSION_RW_VERSION: 'bloonchipper_v2.0.13589-727a419',
123*9c5db199SXin Li                _FIRMWARE_VERSION_KEY_ID: '1c590ef36399f6a2b2ef87079c135b69ef89eb60',
124*9c5db199SXin Li            },
125*9c5db199SXin Li            'bloonchipper_v2.0.5938-197506c1-RO_v2.0.13589-727a419-RW.bin': {
126*9c5db199SXin Li                _FIRMWARE_VERSION_SHA256SUM: 'dfa1a9e409893441c990edde86dbe6d0e301c03b7a9e604ec6af5fc1691ef1be',
127*9c5db199SXin Li                _FIRMWARE_VERSION_RO_VERSION: 'bloonchipper_v2.0.5938-197506c1',
128*9c5db199SXin Li                _FIRMWARE_VERSION_RW_VERSION: 'bloonchipper_v2.0.13589-727a419',
129*9c5db199SXin Li                _FIRMWARE_VERSION_KEY_ID: '1c590ef36399f6a2b2ef87079c135b69ef89eb60',
130*9c5db199SXin Li            },
131*9c5db199SXin Li        },
132*9c5db199SXin Li        _FP_BOARD_NAME_NOCTURNE: {
133*9c5db199SXin Li            'nocturne_fp_v2.2.64-58cf5974e-RO_v2.0.13584-6fcfe697-RW.bin': {
134*9c5db199SXin Li                _FIRMWARE_VERSION_SHA256SUM: '8ebc978bf18fc1629a8ab9b33ac91817d850ce5ca9c55dc69c99b0acfb540948',
135*9c5db199SXin Li                _FIRMWARE_VERSION_RO_VERSION: 'nocturne_fp_v2.2.64-58cf5974e',
136*9c5db199SXin Li                _FIRMWARE_VERSION_RW_VERSION: 'nocturne_fp_v2.0.13584-6fcfe697',
137*9c5db199SXin Li                _FIRMWARE_VERSION_KEY_ID: '6f38c866182bd9bf7a4462c06ac04fa6a0074351',
138*9c5db199SXin Li            },
139*9c5db199SXin Li        },
140*9c5db199SXin Li        _FP_BOARD_NAME_NAMI: {
141*9c5db199SXin Li            'nami_fp_v2.2.144-7a08e07eb-RO_v2.0.13584-6fcfe69780-RW.bin': {
142*9c5db199SXin Li                _FIRMWARE_VERSION_SHA256SUM: 'e198db08020ac71a11a53d641d6ada750061fb3f3faa2728aab7835266ed9e7b',
143*9c5db199SXin Li                _FIRMWARE_VERSION_RO_VERSION: 'nami_fp_v2.2.144-7a08e07eb',
144*9c5db199SXin Li                _FIRMWARE_VERSION_RW_VERSION: 'nami_fp_v2.0.13584-6fcfe69780',
145*9c5db199SXin Li                _FIRMWARE_VERSION_KEY_ID: '35486c0090ca390408f1fbbf2a182966084fe2f8',
146*9c5db199SXin Li            },
147*9c5db199SXin Li        },
148*9c5db199SXin Li        _FP_BOARD_NAME_DARTMONKEY: {
149*9c5db199SXin Li            'dartmonkey_v2.0.2887-311310808-RO_v2.0.13584-6fcfe6978-RW.bin': {
150*9c5db199SXin Li                _FIRMWARE_VERSION_SHA256SUM: '8fa168c19d886b5fe8e852bba7d3b04cd0cd2344d377d9b3d278a45d76b206a1',
151*9c5db199SXin Li                _FIRMWARE_VERSION_RO_VERSION: 'dartmonkey_v2.0.2887-311310808',
152*9c5db199SXin Li                _FIRMWARE_VERSION_RW_VERSION: 'dartmonkey_v2.0.13584-6fcfe6978',
153*9c5db199SXin Li                _FIRMWARE_VERSION_KEY_ID: '257a0aa3ac9e81aa4bc3aabdb6d3d079117c5799',
154*9c5db199SXin Li            }
155*9c5db199SXin Li        }
156*9c5db199SXin Li    }
157*9c5db199SXin Li
158*9c5db199SXin Li    _BIOD_UPSTART_JOB_NAME = 'biod'
159*9c5db199SXin Li    _POWERD_UPSTART_JOB_NAME = 'powerd'
160*9c5db199SXin Li    # TODO(crbug.com/925545)
161*9c5db199SXin Li    _TIMBERSLIDE_UPSTART_JOB_NAME = \
162*9c5db199SXin Li        'timberslide LOG_PATH=/sys/kernel/debug/cros_fp/console_log'
163*9c5db199SXin Li
164*9c5db199SXin Li    _INIT_ENTROPY_CMD = 'bio_wash --factory_init'
165*9c5db199SXin Li
166*9c5db199SXin Li    _CROS_FP_ARG = '--name=cros_fp'
167*9c5db199SXin Li    _CROS_CONFIG_FINGERPRINT_PATH = '/fingerprint'
168*9c5db199SXin Li    _ECTOOL_RO_VERSION = 'RO version'
169*9c5db199SXin Li    _ECTOOL_RW_VERSION = 'RW version'
170*9c5db199SXin Li    _ECTOOL_FIRMWARE_COPY = 'Firmware copy'
171*9c5db199SXin Li    _ECTOOL_ROLLBACK_BLOCK_ID = 'Rollback block id'
172*9c5db199SXin Li    _ECTOOL_ROLLBACK_MIN_VERSION = 'Rollback min version'
173*9c5db199SXin Li    _ECTOOL_ROLLBACK_RW_VERSION = 'RW rollback version'
174*9c5db199SXin Li
175*9c5db199SXin Li    @staticmethod
176*9c5db199SXin Li    def _parse_colon_delimited_output(ectool_output):
177*9c5db199SXin Li        """
178*9c5db199SXin Li        Converts ectool's (or any other tool with similar output) colon
179*9c5db199SXin Li        delimited output into python dict. Ignores any lines that do not have
180*9c5db199SXin Li        colons.
181*9c5db199SXin Li
182*9c5db199SXin Li        Example:
183*9c5db199SXin Li        RO version:    nocturne_fp_v2.2.64-58cf5974e
184*9c5db199SXin Li        RW version:    nocturne_fp_v2.2.110-b936c0a3c
185*9c5db199SXin Li
186*9c5db199SXin Li        becomes:
187*9c5db199SXin Li        {
188*9c5db199SXin Li          'RO version': 'nocturne_fp_v2.2.64-58cf5974e',
189*9c5db199SXin Li          'RW version': 'nocturne_fp_v2.2.110-b936c0a3c'
190*9c5db199SXin Li        }
191*9c5db199SXin Li        """
192*9c5db199SXin Li        ret = {}
193*9c5db199SXin Li        try:
194*9c5db199SXin Li            for line in ectool_output.strip().split('\n'):
195*9c5db199SXin Li                splits = line.split(':', 1)
196*9c5db199SXin Li                if len(splits) != 2:
197*9c5db199SXin Li                    continue
198*9c5db199SXin Li                key = splits[0].strip()
199*9c5db199SXin Li                val = splits[1].strip()
200*9c5db199SXin Li                ret[key] = val
201*9c5db199SXin Li        except:
202*9c5db199SXin Li            raise error.TestFail('Unable to parse ectool output: %s'
203*9c5db199SXin Li                                 % ectool_output)
204*9c5db199SXin Li        return ret
205*9c5db199SXin Li
206*9c5db199SXin Li    def initialize(self, host):
207*9c5db199SXin Li        """Perform minimal initialization, to avoid AttributeError in cleanup"""
208*9c5db199SXin Li        self.host = host
209*9c5db199SXin Li        self.servo = host.servo
210*9c5db199SXin Li
211*9c5db199SXin Li        self._validate_compatible_servo_version()
212*9c5db199SXin Li
213*9c5db199SXin Li        self.servo.initialize_dut()
214*9c5db199SXin Li
215*9c5db199SXin Li        self.fp_board = self.get_fp_board()
216*9c5db199SXin Li        self._build_fw_file = self.get_build_fw_file()
217*9c5db199SXin Li
218*9c5db199SXin Li    def setup_test(self, test_dir, use_dev_signed_fw=False,
219*9c5db199SXin Li                   enable_hardware_write_protect=True,
220*9c5db199SXin Li                   enable_software_write_protect=True,
221*9c5db199SXin Li                   force_firmware_flashing=False, init_entropy=True):
222*9c5db199SXin Li        """Perform more complete initialization, including copying test files"""
223*9c5db199SXin Li        logging.info('HW write protect enabled: %s',
224*9c5db199SXin Li                     self.is_hardware_write_protect_enabled())
225*9c5db199SXin Li
226*9c5db199SXin Li        # TODO(crbug.com/925545): stop timberslide so /var/log/cros_fp.log
227*9c5db199SXin Li        # continues to update after flashing.
228*9c5db199SXin Li        self._timberslide_running = self.host.upstart_status(
229*9c5db199SXin Li            self._TIMBERSLIDE_UPSTART_JOB_NAME)
230*9c5db199SXin Li        if self._timberslide_running:
231*9c5db199SXin Li            logging.info('Stopping %s', self._TIMBERSLIDE_UPSTART_JOB_NAME)
232*9c5db199SXin Li            self.host.upstart_stop(self._TIMBERSLIDE_UPSTART_JOB_NAME)
233*9c5db199SXin Li
234*9c5db199SXin Li        self._biod_running = self.host.upstart_status(
235*9c5db199SXin Li            self._BIOD_UPSTART_JOB_NAME)
236*9c5db199SXin Li        if self._biod_running:
237*9c5db199SXin Li            logging.info('Stopping %s', self._BIOD_UPSTART_JOB_NAME)
238*9c5db199SXin Li            self.host.upstart_stop(self._BIOD_UPSTART_JOB_NAME)
239*9c5db199SXin Li
240*9c5db199SXin Li        # TODO(b/183123775): Remove when bug is fixed.
241*9c5db199SXin Li        #  Disabling powerd to prevent the display from turning off, which kills
242*9c5db199SXin Li        #  USB on some platforms.
243*9c5db199SXin Li        self._powerd_running = self.host.upstart_status(
244*9c5db199SXin Li            self._POWERD_UPSTART_JOB_NAME)
245*9c5db199SXin Li        if self._powerd_running:
246*9c5db199SXin Li            logging.info('Stopping %s', self._POWERD_UPSTART_JOB_NAME)
247*9c5db199SXin Li            self.host.upstart_stop(self._POWERD_UPSTART_JOB_NAME)
248*9c5db199SXin Li
249*9c5db199SXin Li        # On some platforms an AP reboot is needed after flashing firmware to
250*9c5db199SXin Li        # rebind the driver.
251*9c5db199SXin Li        self._dut_needs_reboot = self.is_uart_device()
252*9c5db199SXin Li
253*9c5db199SXin Li        if filesystem_util.is_rootfs_writable(self.host):
254*9c5db199SXin Li            if self._dut_needs_reboot:
255*9c5db199SXin Li                logging.warning('rootfs is writable')
256*9c5db199SXin Li            else:
257*9c5db199SXin Li                raise error.TestFail('rootfs is writable')
258*9c5db199SXin Li
259*9c5db199SXin Li        if not self.biod_upstart_job_enabled():
260*9c5db199SXin Li            raise error.TestFail(
261*9c5db199SXin Li                    'Biod upstart job is disabled at the beginning of test')
262*9c5db199SXin Li        if not self.fp_updater_is_enabled():
263*9c5db199SXin Li            raise error.TestFail(
264*9c5db199SXin Li                    'Fingerprint firmware updater is disabled at the beginning of test'
265*9c5db199SXin Li            )
266*9c5db199SXin Li
267*9c5db199SXin Li        # Disable biod and updater so that they won't interfere after reboot.
268*9c5db199SXin Li        if self._dut_needs_reboot:
269*9c5db199SXin Li            self.disable_biod_upstart_job()
270*9c5db199SXin Li            self.disable_fp_updater()
271*9c5db199SXin Li
272*9c5db199SXin Li        # create tmp working directory on device (automatically cleaned up)
273*9c5db199SXin Li        self._dut_working_dir = self.host.get_tmp_dir(
274*9c5db199SXin Li            parent=self._DUT_TMP_PATH_BASE)
275*9c5db199SXin Li        logging.info('Created dut_working_dir: %s', self._dut_working_dir)
276*9c5db199SXin Li        self.copy_files_to_dut(test_dir, self._dut_working_dir)
277*9c5db199SXin Li
278*9c5db199SXin Li        self.validate_build_fw_file()
279*9c5db199SXin Li
280*9c5db199SXin Li        gen_script = os.path.abspath(os.path.join(self.autodir,
281*9c5db199SXin Li                                                  'server', 'cros', 'faft',
282*9c5db199SXin Li                                                  self._GENIMAGES_SCRIPT_NAME))
283*9c5db199SXin Li        self._dut_firmware_test_images_dir = \
284*9c5db199SXin Li            self._generate_test_firmware_images(gen_script,
285*9c5db199SXin Li                                                self._build_fw_file,
286*9c5db199SXin Li                                                self._dut_working_dir)
287*9c5db199SXin Li        logging.info('dut_firmware_test_images_dir: %s',
288*9c5db199SXin Li                     self._dut_firmware_test_images_dir)
289*9c5db199SXin Li
290*9c5db199SXin Li        self._initialize_test_firmware_image_attrs(
291*9c5db199SXin Li            self._dut_firmware_test_images_dir)
292*9c5db199SXin Li
293*9c5db199SXin Li        self._initialize_running_fw_version(use_dev_signed_fw,
294*9c5db199SXin Li                                            force_firmware_flashing)
295*9c5db199SXin Li
296*9c5db199SXin Li        if init_entropy:
297*9c5db199SXin Li            self._initialize_fw_entropy()
298*9c5db199SXin Li
299*9c5db199SXin Li        self._initialize_hw_and_sw_write_protect(enable_hardware_write_protect,
300*9c5db199SXin Li                                                 enable_software_write_protect)
301*9c5db199SXin Li
302*9c5db199SXin Li    def cleanup(self):
303*9c5db199SXin Li        """Restores original state."""
304*9c5db199SXin Li        if hasattr(self, '_need_fw_restore') and self._need_fw_restore:
305*9c5db199SXin Li            # Once the tests complete we need to make sure we're running the
306*9c5db199SXin Li            # original firmware (not dev version) and potentially reset rollback.
307*9c5db199SXin Li            self._initialize_running_fw_version(use_dev_signed_fw=False,
308*9c5db199SXin Li                                                force_firmware_flashing=False)
309*9c5db199SXin Li            self._initialize_fw_entropy()
310*9c5db199SXin Li
311*9c5db199SXin Li        # Re-enable biod and updater after flashing and initializing entropy so
312*9c5db199SXin Li        # that they don't interfere if there was a reboot.
313*9c5db199SXin Li        if hasattr(self, '_dut_needs_reboot') and self._dut_needs_reboot:
314*9c5db199SXin Li            if not self.biod_upstart_job_enabled():
315*9c5db199SXin Li                self.enable_biod_upstart_job()
316*9c5db199SXin Li            if not self.fp_updater_is_enabled():
317*9c5db199SXin Li                self.enable_fp_updater()
318*9c5db199SXin Li        self._initialize_hw_and_sw_write_protect(
319*9c5db199SXin Li            enable_hardware_write_protect=True,
320*9c5db199SXin Li            enable_software_write_protect=True)
321*9c5db199SXin Li        # TODO(b/183123775)
322*9c5db199SXin Li        if hasattr(self, '_powerd_running') and self._powerd_running:
323*9c5db199SXin Li            logging.info('Restarting powerd')
324*9c5db199SXin Li            self.host.upstart_restart(self._POWERD_UPSTART_JOB_NAME)
325*9c5db199SXin Li        if hasattr(self, '_biod_running') and self._biod_running:
326*9c5db199SXin Li            logging.info('Restarting biod')
327*9c5db199SXin Li            self.host.upstart_restart(self._BIOD_UPSTART_JOB_NAME)
328*9c5db199SXin Li        # TODO(crbug.com/925545)
329*9c5db199SXin Li        if hasattr(self, '_timberslide_running') and self._timberslide_running:
330*9c5db199SXin Li            logging.info('Restarting timberslide')
331*9c5db199SXin Li            self.host.upstart_restart(self._TIMBERSLIDE_UPSTART_JOB_NAME)
332*9c5db199SXin Li
333*9c5db199SXin Li        super(FingerprintTest, self).cleanup()
334*9c5db199SXin Li
335*9c5db199SXin Li    def after_run_once(self):
336*9c5db199SXin Li        """Logs which iteration just ran."""
337*9c5db199SXin Li        logging.info('successfully ran iteration %d', self.iteration)
338*9c5db199SXin Li
339*9c5db199SXin Li    def _validate_compatible_servo_version(self):
340*9c5db199SXin Li        """Asserts if a compatible servo version is not attached."""
341*9c5db199SXin Li        servo_version = self.servo.get_servo_version()
342*9c5db199SXin Li        logging.info('servo version: %s', servo_version)
343*9c5db199SXin Li
344*9c5db199SXin Li    def _generate_test_firmware_images(self, gen_script, build_fw_file,
345*9c5db199SXin Li                                       dut_working_dir):
346*9c5db199SXin Li        """
347*9c5db199SXin Li        Copies the fingerprint firmware from the DUT to the server running
348*9c5db199SXin Li        the tests, which runs a script to generate various test versions of
349*9c5db199SXin Li        the firmware.
350*9c5db199SXin Li
351*9c5db199SXin Li        @return full path to location of test images on DUT
352*9c5db199SXin Li        """
353*9c5db199SXin Li        # create subdirectory under existing tmp dir
354*9c5db199SXin Li        server_tmp_dir = os.path.join(self.tmpdir,
355*9c5db199SXin Li                                      self._SERVER_GENERATED_FW_DIR_NAME)
356*9c5db199SXin Li        os.mkdir(server_tmp_dir)
357*9c5db199SXin Li        logging.info('server_tmp_dir: %s', server_tmp_dir)
358*9c5db199SXin Li
359*9c5db199SXin Li        # Copy firmware from device to server
360*9c5db199SXin Li        self.get_files_from_dut(build_fw_file, server_tmp_dir)
361*9c5db199SXin Li
362*9c5db199SXin Li        # Run the test image generation script on server
363*9c5db199SXin Li        pushd = os.getcwd()
364*9c5db199SXin Li        os.chdir(server_tmp_dir)
365*9c5db199SXin Li        cmd = ' '.join([gen_script,
366*9c5db199SXin Li                        self.get_fp_board(),
367*9c5db199SXin Li                        os.path.basename(build_fw_file)])
368*9c5db199SXin Li        result = self.run_server_cmd(cmd)
369*9c5db199SXin Li        if result.exit_status != 0:
370*9c5db199SXin Li            raise error.TestFail('Failed to run test image generation script')
371*9c5db199SXin Li
372*9c5db199SXin Li        os.chdir(pushd)
373*9c5db199SXin Li
374*9c5db199SXin Li        # Copy resulting files to DUT tmp dir
375*9c5db199SXin Li        server_generated_images_dir = \
376*9c5db199SXin Li            os.path.join(server_tmp_dir, self._GENIMAGES_OUTPUT_DIR_NAME)
377*9c5db199SXin Li        self.copy_files_to_dut(server_generated_images_dir, dut_working_dir)
378*9c5db199SXin Li
379*9c5db199SXin Li        return os.path.join(dut_working_dir, self._GENIMAGES_OUTPUT_DIR_NAME)
380*9c5db199SXin Li
381*9c5db199SXin Li    def _initialize_test_firmware_image_attrs(self, dut_fw_test_images_dir):
382*9c5db199SXin Li        """Sets attributes with full path to test images on DUT.
383*9c5db199SXin Li
384*9c5db199SXin Li        Example: self.TEST_IMAGE_DEV = /some/path/images/nocturne_fp.dev
385*9c5db199SXin Li        """
386*9c5db199SXin Li        for key, val in six.iteritems(self._TEST_IMAGE_FORMAT_MAP):
387*9c5db199SXin Li            full_path = os.path.join(dut_fw_test_images_dir,
388*9c5db199SXin Li                                     val % self.get_fp_board())
389*9c5db199SXin Li            setattr(self, key, full_path)
390*9c5db199SXin Li
391*9c5db199SXin Li    def _initialize_running_fw_version(self, use_dev_signed_fw,
392*9c5db199SXin Li                                       force_firmware_flashing):
393*9c5db199SXin Li        """
394*9c5db199SXin Li        Ensures that the running firmware version matches build version
395*9c5db199SXin Li        and factory rollback settings; flashes to correct version if either
396*9c5db199SXin Li        fails to match is requested to force flashing.
397*9c5db199SXin Li
398*9c5db199SXin Li        RO firmware: original version released at factory
399*9c5db199SXin Li        RW firmware: firmware from current build
400*9c5db199SXin Li        """
401*9c5db199SXin Li        build_rw_firmware_version = \
402*9c5db199SXin Li            self.get_build_rw_firmware_version(use_dev_signed_fw)
403*9c5db199SXin Li        golden_ro_firmware_version = \
404*9c5db199SXin Li            self.get_golden_ro_firmware_version(use_dev_signed_fw)
405*9c5db199SXin Li        logging.info('Build RW firmware version: %s', build_rw_firmware_version)
406*9c5db199SXin Li        logging.info('Golden RO firmware version: %s',
407*9c5db199SXin Li                     golden_ro_firmware_version)
408*9c5db199SXin Li
409*9c5db199SXin Li        running_rw_firmware = self.ensure_running_rw_firmware()
410*9c5db199SXin Li
411*9c5db199SXin Li        fw_versions_match = self.running_fw_version_matches_given_version(
412*9c5db199SXin Li            build_rw_firmware_version, golden_ro_firmware_version)
413*9c5db199SXin Li
414*9c5db199SXin Li        if not running_rw_firmware or not fw_versions_match \
415*9c5db199SXin Li            or not self.is_rollback_set_to_initial_val() \
416*9c5db199SXin Li            or force_firmware_flashing:
417*9c5db199SXin Li            fw_file = self._build_fw_file
418*9c5db199SXin Li            if use_dev_signed_fw:
419*9c5db199SXin Li                fw_file = self.TEST_IMAGE_DEV
420*9c5db199SXin Li            self.flash_rw_ro_firmware(fw_file)
421*9c5db199SXin Li            if not self.running_fw_version_matches_given_version(
422*9c5db199SXin Li                build_rw_firmware_version, golden_ro_firmware_version):
423*9c5db199SXin Li                raise error.TestFail(
424*9c5db199SXin Li                    'Running firmware version does not match expected version')
425*9c5db199SXin Li
426*9c5db199SXin Li    def _initialize_fw_entropy(self):
427*9c5db199SXin Li        """Sets the entropy (key) in FPMCU flash (if not set)."""
428*9c5db199SXin Li        result = self.run_cmd(self._INIT_ENTROPY_CMD)
429*9c5db199SXin Li        if result.exit_status != 0:
430*9c5db199SXin Li            raise error.TestFail('Unable to initialize entropy')
431*9c5db199SXin Li
432*9c5db199SXin Li    def _initialize_hw_and_sw_write_protect(self, enable_hardware_write_protect,
433*9c5db199SXin Li                                            enable_software_write_protect):
434*9c5db199SXin Li        """Enables/disables hardware/software write protect."""
435*9c5db199SXin Li        # sw: 0, hw: 0 => initial_hw(0) -> sw(0) -> hw(0)
436*9c5db199SXin Li        # sw: 0, hw: 1 => initial_hw(0) -> sw(0) -> hw(1)
437*9c5db199SXin Li        # sw: 1, hw: 0 => initial_hw(1) -> sw(1) -> hw(0)
438*9c5db199SXin Li        # sw: 1, hw: 1 => initial_hw(1) -> sw(1) -> hw(1)
439*9c5db199SXin Li        hardware_write_protect_initial_enabled = True
440*9c5db199SXin Li        if not enable_software_write_protect:
441*9c5db199SXin Li            hardware_write_protect_initial_enabled = False
442*9c5db199SXin Li
443*9c5db199SXin Li        self.set_hardware_write_protect(hardware_write_protect_initial_enabled)
444*9c5db199SXin Li
445*9c5db199SXin Li        self.set_software_write_protect(enable_software_write_protect)
446*9c5db199SXin Li        self.set_hardware_write_protect(enable_hardware_write_protect)
447*9c5db199SXin Li
448*9c5db199SXin Li    def get_fp_board(self):
449*9c5db199SXin Li        """Returns name of fingerprint EC.
450*9c5db199SXin Li
451*9c5db199SXin Li        nocturne and nami are special cases and have "_fp" appended. Newer
452*9c5db199SXin Li        FPMCUs have unique names.
453*9c5db199SXin Li        See go/cros-fingerprint-firmware-branching-and-signing.
454*9c5db199SXin Li        """
455*9c5db199SXin Li        # Use cros_config to get fingerprint board.
456*9c5db199SXin Li        # Due to b/160271883, we will try running the cmd via cat instead.
457*9c5db199SXin Li        result = self._run_cros_config_cmd_cat('fingerprint/board')
458*9c5db199SXin Li        if result.exit_status != 0:
459*9c5db199SXin Li            raise error.TestFail(
460*9c5db199SXin Li                'Unable to get fingerprint board with cros_config')
461*9c5db199SXin Li        return result.stdout.rstrip()
462*9c5db199SXin Li
463*9c5db199SXin Li    def is_uart_device(self) -> bool:
464*9c5db199SXin Li        """Returns True if the boards transpot device is UART"""
465*9c5db199SXin Li        uart_devices = ['zork', 'guybrush']
466*9c5db199SXin Li        return self.get_host_board() in uart_devices
467*9c5db199SXin Li
468*9c5db199SXin Li    def get_host_board(self):
469*9c5db199SXin Li        """Returns name of the host board."""
470*9c5db199SXin Li        return self.host.get_board().split(':')[-1]
471*9c5db199SXin Li
472*9c5db199SXin Li    def get_build_fw_file(self):
473*9c5db199SXin Li        """Returns full path to build FW file on DUT."""
474*9c5db199SXin Li        ls_cmd = 'ls %s/%s*.bin' % (
475*9c5db199SXin Li            self._FINGERPRINT_BUILD_FW_DIR, self.fp_board)
476*9c5db199SXin Li        result = self.run_cmd(ls_cmd)
477*9c5db199SXin Li        if result.exit_status != 0:
478*9c5db199SXin Li            raise error.TestFail(
479*9c5db199SXin Li                'Unable to find firmware file on device:'
480*9c5db199SXin Li                ' command failed (rc=%s): %s'
481*9c5db199SXin Li                % (result.exit_status, result.stderr.strip() or ls_cmd))
482*9c5db199SXin Li        ret = result.stdout.rstrip()
483*9c5db199SXin Li        logging.info('Build firmware file: %s', ret)
484*9c5db199SXin Li        return ret
485*9c5db199SXin Li
486*9c5db199SXin Li    def check_equal(self, a, b):
487*9c5db199SXin Li        """Raises exception if "a" does not equal "b"."""
488*9c5db199SXin Li        if a != b:
489*9c5db199SXin Li            raise error.TestFail('"%s" does not match expected "%s" for board '
490*9c5db199SXin Li                                 '%s' % (a, b, self.get_fp_board()))
491*9c5db199SXin Li
492*9c5db199SXin Li    def validate_build_fw_file(self,
493*9c5db199SXin Li                               allowed_types=(_KEY_TYPE_PRE_MP, _KEY_TYPE_MP)):
494*9c5db199SXin Li        """
495*9c5db199SXin Li        Checks that all attributes in the given firmware file match their
496*9c5db199SXin Li        expected values.
497*9c5db199SXin Li
498*9c5db199SXin Li        @param allowed_types: If key type is something else, raise TestFail.
499*9c5db199SXin Li                              Default: pre-MP or MP.
500*9c5db199SXin Li        @type allowed_types: tuple | list
501*9c5db199SXin Li        """
502*9c5db199SXin Li        build_fw_file = self._build_fw_file
503*9c5db199SXin Li        # check hash
504*9c5db199SXin Li        actual_hash = self._calculate_sha256sum(build_fw_file)
505*9c5db199SXin Li        expected_hash = self._get_expected_firmware_hash(build_fw_file)
506*9c5db199SXin Li        self.check_equal(actual_hash, expected_hash)
507*9c5db199SXin Li
508*9c5db199SXin Li        # check signing key_id
509*9c5db199SXin Li        actual_key_id = self._read_firmware_key_id(build_fw_file)
510*9c5db199SXin Li        expected_key_id = self._get_expected_firmware_key_id(build_fw_file)
511*9c5db199SXin Li        self.check_equal(actual_key_id, expected_key_id)
512*9c5db199SXin Li
513*9c5db199SXin Li        # check that the signing key for firmware in the build
514*9c5db199SXin Li        # is "pre mass production" (pre-mp) or "mass production" (MP)
515*9c5db199SXin Li        key_type = self._get_key_type(actual_key_id)
516*9c5db199SXin Li        if key_type not in allowed_types:
517*9c5db199SXin Li            raise error.TestFail(
518*9c5db199SXin Li                'Firmware key type must be %s for board %s; got %s (%s)' %
519*9c5db199SXin Li                (' or '.join(allowed_types), self.fp_board, key_type,
520*9c5db199SXin Li                 actual_key_id))
521*9c5db199SXin Li
522*9c5db199SXin Li        # check ro_version
523*9c5db199SXin Li        actual_ro_version = self._read_firmware_ro_version(build_fw_file)
524*9c5db199SXin Li        expected_ro_version = \
525*9c5db199SXin Li            self._get_expected_firmware_ro_version(build_fw_file)
526*9c5db199SXin Li        self.check_equal(actual_ro_version, expected_ro_version)
527*9c5db199SXin Li
528*9c5db199SXin Li        # check rw_version
529*9c5db199SXin Li        actual_rw_version = self._read_firmware_rw_version(build_fw_file)
530*9c5db199SXin Li        expected_rw_version = \
531*9c5db199SXin Li            self._get_expected_firmware_rw_version(build_fw_file)
532*9c5db199SXin Li        self.check_equal(actual_rw_version, expected_rw_version)
533*9c5db199SXin Li
534*9c5db199SXin Li        logging.info("Validated build firmware metadata.")
535*9c5db199SXin Li
536*9c5db199SXin Li    def _get_key_type(self, key_id):
537*9c5db199SXin Li        """Returns the key "type" for a given "key id"."""
538*9c5db199SXin Li        key_type = self._KEY_ID_MAP_.get(key_id)
539*9c5db199SXin Li        if key_type is None:
540*9c5db199SXin Li            raise error.TestFail('Unable to get key type for key id: %s'
541*9c5db199SXin Li                                 % key_id)
542*9c5db199SXin Li        return key_type
543*9c5db199SXin Li
544*9c5db199SXin Li    def _get_expected_firmware_info(self, build_fw_file, info_type):
545*9c5db199SXin Li        """
546*9c5db199SXin Li        Returns expected firmware info for a given firmware file name.
547*9c5db199SXin Li        """
548*9c5db199SXin Li        build_fw_file_name = os.path.basename(build_fw_file)
549*9c5db199SXin Li
550*9c5db199SXin Li        board = self.get_fp_board()
551*9c5db199SXin Li        board_expected_fw_info = self._FIRMWARE_VERSION_MAP.get(board)
552*9c5db199SXin Li        if board_expected_fw_info is None:
553*9c5db199SXin Li            raise error.TestFail('Unable to get firmware info for board: %s'
554*9c5db199SXin Li                                 % board)
555*9c5db199SXin Li
556*9c5db199SXin Li        expected_fw_info = board_expected_fw_info.get(build_fw_file_name)
557*9c5db199SXin Li        if expected_fw_info is None:
558*9c5db199SXin Li            raise error.TestFail('Unable to get firmware info for file: %s'
559*9c5db199SXin Li                                 % build_fw_file_name)
560*9c5db199SXin Li
561*9c5db199SXin Li        ret = expected_fw_info.get(info_type)
562*9c5db199SXin Li        if ret is None:
563*9c5db199SXin Li            raise error.TestFail('Unable to get firmware info type: %s'
564*9c5db199SXin Li                                 % info_type)
565*9c5db199SXin Li
566*9c5db199SXin Li        return ret
567*9c5db199SXin Li
568*9c5db199SXin Li    def _get_expected_firmware_hash(self, build_fw_file):
569*9c5db199SXin Li        """Returns expected hash of firmware file."""
570*9c5db199SXin Li        return self._get_expected_firmware_info(
571*9c5db199SXin Li            build_fw_file, self._FIRMWARE_VERSION_SHA256SUM)
572*9c5db199SXin Li
573*9c5db199SXin Li    def _get_expected_firmware_key_id(self, build_fw_file):
574*9c5db199SXin Li        """Returns expected "key id" for firmware file."""
575*9c5db199SXin Li        return self._get_expected_firmware_info(
576*9c5db199SXin Li            build_fw_file, self._FIRMWARE_VERSION_KEY_ID)
577*9c5db199SXin Li
578*9c5db199SXin Li    def _get_expected_firmware_ro_version(self, build_fw_file):
579*9c5db199SXin Li        """Returns expected RO version for firmware file."""
580*9c5db199SXin Li        return self._get_expected_firmware_info(
581*9c5db199SXin Li            build_fw_file, self._FIRMWARE_VERSION_RO_VERSION)
582*9c5db199SXin Li
583*9c5db199SXin Li    def _get_expected_firmware_rw_version(self, build_fw_file):
584*9c5db199SXin Li        """Returns expected RW version for firmware file."""
585*9c5db199SXin Li        return self._get_expected_firmware_info(
586*9c5db199SXin Li            build_fw_file, self._FIRMWARE_VERSION_RW_VERSION)
587*9c5db199SXin Li
588*9c5db199SXin Li    def _read_firmware_key_id(self, file_name):
589*9c5db199SXin Li        """Returns "key id" as read from the given file."""
590*9c5db199SXin Li        result = self._run_futility_show_cmd(file_name)
591*9c5db199SXin Li        parsed = self._parse_colon_delimited_output(result)
592*9c5db199SXin Li        key_id = parsed.get(self._FUTILITY_KEY_ID_KEY_NAME)
593*9c5db199SXin Li        if key_id is None:
594*9c5db199SXin Li            raise error.TestFail('Failed to get key ID for file: %s'
595*9c5db199SXin Li                                 % file_name)
596*9c5db199SXin Li        return key_id
597*9c5db199SXin Li
598*9c5db199SXin Li    def _read_firmware_ro_version(self, file_name):
599*9c5db199SXin Li        """Returns RO firmware version as read from the given file."""
600*9c5db199SXin Li        return self._run_dump_fmap_cmd(file_name, 'RO_FRID')
601*9c5db199SXin Li
602*9c5db199SXin Li    def _read_firmware_rw_version(self, file_name):
603*9c5db199SXin Li        """Returns RW firmware version as read from the given file."""
604*9c5db199SXin Li        return self._run_dump_fmap_cmd(file_name, 'RW_FWID')
605*9c5db199SXin Li
606*9c5db199SXin Li    def _calculate_sha256sum(self, file_name):
607*9c5db199SXin Li        """Returns SHA256 hash of the given file contents."""
608*9c5db199SXin Li        result = self._run_sha256sum_cmd(file_name)
609*9c5db199SXin Li        return result.stdout.split()[0]
610*9c5db199SXin Li
611*9c5db199SXin Li    def _get_running_firmware_info(self, key):
612*9c5db199SXin Li        """
613*9c5db199SXin Li        Returns requested firmware info (RW version, RO version, or firmware
614*9c5db199SXin Li        type).
615*9c5db199SXin Li        """
616*9c5db199SXin Li        result = self._run_ectool_cmd('version')
617*9c5db199SXin Li        parsed = self._parse_colon_delimited_output(result.stdout)
618*9c5db199SXin Li        if result.exit_status != 0:
619*9c5db199SXin Li            raise error.TestFail('Failed to get running firmware info')
620*9c5db199SXin Li        info = parsed.get(key)
621*9c5db199SXin Li        if info is None:
622*9c5db199SXin Li            raise error.TestFail(
623*9c5db199SXin Li                'Failed to get running firmware info: %s' % key)
624*9c5db199SXin Li        return info
625*9c5db199SXin Li
626*9c5db199SXin Li    def get_running_rw_firmware_version(self):
627*9c5db199SXin Li        """Returns running RW firmware version."""
628*9c5db199SXin Li        return self._get_running_firmware_info(self._ECTOOL_RW_VERSION)
629*9c5db199SXin Li
630*9c5db199SXin Li    def get_running_ro_firmware_version(self):
631*9c5db199SXin Li        """Returns running RO firmware version."""
632*9c5db199SXin Li        return self._get_running_firmware_info(self._ECTOOL_RO_VERSION)
633*9c5db199SXin Li
634*9c5db199SXin Li    def get_running_firmware_type(self):
635*9c5db199SXin Li        """Returns type of firmware we are running (RW or RO)."""
636*9c5db199SXin Li        return self._get_running_firmware_info(self._ECTOOL_FIRMWARE_COPY)
637*9c5db199SXin Li
638*9c5db199SXin Li    def _get_rollback_info(self, info_type):
639*9c5db199SXin Li        """Returns requested type of rollback info."""
640*9c5db199SXin Li        result = self._run_ectool_cmd('rollbackinfo')
641*9c5db199SXin Li        parsed = self._parse_colon_delimited_output(result.stdout)
642*9c5db199SXin Li        if result.exit_status != 0:
643*9c5db199SXin Li            raise error.TestFail('Failed to get rollback info')
644*9c5db199SXin Li        info = parsed.get(info_type)
645*9c5db199SXin Li        if info is None:
646*9c5db199SXin Li            raise error.TestFail('Failed to get rollback info: %s' % info_type)
647*9c5db199SXin Li        return info
648*9c5db199SXin Li
649*9c5db199SXin Li    def get_rollback_id(self):
650*9c5db199SXin Li        """Returns rollback ID."""
651*9c5db199SXin Li        return self._get_rollback_info(self._ECTOOL_ROLLBACK_BLOCK_ID)
652*9c5db199SXin Li
653*9c5db199SXin Li    def get_rollback_min_version(self):
654*9c5db199SXin Li        """Returns rollback min version."""
655*9c5db199SXin Li        return self._get_rollback_info(self._ECTOOL_ROLLBACK_MIN_VERSION)
656*9c5db199SXin Li
657*9c5db199SXin Li    def get_rollback_rw_version(self):
658*9c5db199SXin Li        """Returns RW rollback version."""
659*9c5db199SXin Li        return self._get_rollback_info(self._ECTOOL_ROLLBACK_RW_VERSION)
660*9c5db199SXin Li
661*9c5db199SXin Li    def _construct_dev_version(self, orig_version):
662*9c5db199SXin Li        """
663*9c5db199SXin Li        Given a "regular" version string from a signed build, returns the
664*9c5db199SXin Li        special "dev" version that we use when creating the test images.
665*9c5db199SXin Li        """
666*9c5db199SXin Li        fw_version = orig_version
667*9c5db199SXin Li        if len(fw_version) + len('.dev') > 31:
668*9c5db199SXin Li            fw_version = fw_version[:27]
669*9c5db199SXin Li        fw_version = fw_version + '.dev'
670*9c5db199SXin Li        return fw_version
671*9c5db199SXin Li
672*9c5db199SXin Li    def get_golden_ro_firmware_version(self, use_dev_signed_fw):
673*9c5db199SXin Li        """Returns RO firmware version used in factory."""
674*9c5db199SXin Li        board = self.get_fp_board()
675*9c5db199SXin Li        golden_version = self._GOLDEN_RO_FIRMWARE_VERSION_MAP.get(board)
676*9c5db199SXin Li        if isinstance(golden_version, dict):
677*9c5db199SXin Li            golden_version = golden_version.get(self.get_host_board())
678*9c5db199SXin Li        if golden_version is None:
679*9c5db199SXin Li            raise error.TestFail('Unable to get golden RO version for board: %s'
680*9c5db199SXin Li                                 % board)
681*9c5db199SXin Li        if use_dev_signed_fw:
682*9c5db199SXin Li            golden_version = self._construct_dev_version(golden_version)
683*9c5db199SXin Li        return golden_version
684*9c5db199SXin Li
685*9c5db199SXin Li    def get_build_rw_firmware_version(self, use_dev_signed_fw):
686*9c5db199SXin Li        """Returns RW firmware version from build."""
687*9c5db199SXin Li        fw_version = self._read_firmware_rw_version(self._build_fw_file)
688*9c5db199SXin Li        if use_dev_signed_fw:
689*9c5db199SXin Li            fw_version = self._construct_dev_version(fw_version)
690*9c5db199SXin Li        return fw_version
691*9c5db199SXin Li
692*9c5db199SXin Li    def ensure_running_rw_firmware(self):
693*9c5db199SXin Li        """
694*9c5db199SXin Li        Check whether the device is running RW firmware. If not, try rebooting
695*9c5db199SXin Li        to RW.
696*9c5db199SXin Li
697*9c5db199SXin Li        @return true if successfully verified running RW firmware, false
698*9c5db199SXin Li        otherwise.
699*9c5db199SXin Li        """
700*9c5db199SXin Li        try:
701*9c5db199SXin Li            if self.get_running_firmware_type() != self._FIRMWARE_TYPE_RW:
702*9c5db199SXin Li                self._reboot_ec()
703*9c5db199SXin Li                if self.get_running_firmware_type() != self._FIRMWARE_TYPE_RW:
704*9c5db199SXin Li                    # RW may be corrupted.
705*9c5db199SXin Li                    return False
706*9c5db199SXin Li        except:
707*9c5db199SXin Li            # We may not always be able to read the firmware version.
708*9c5db199SXin Li            # For example, if the firmware is erased due to RDP1, running any
709*9c5db199SXin Li            # commands (such as getting the version) won't work.
710*9c5db199SXin Li            return False
711*9c5db199SXin Li        return True
712*9c5db199SXin Li
713*9c5db199SXin Li    def running_fw_version_matches_given_version(self, rw_version, ro_version):
714*9c5db199SXin Li        """
715*9c5db199SXin Li        Returns True if the running RO and RW firmware versions match the
716*9c5db199SXin Li        provided versions.
717*9c5db199SXin Li        """
718*9c5db199SXin Li        try:
719*9c5db199SXin Li            running_rw_firmware_version = self.get_running_rw_firmware_version()
720*9c5db199SXin Li            running_ro_firmware_version = self.get_running_ro_firmware_version()
721*9c5db199SXin Li
722*9c5db199SXin Li            logging.info('RW firmware, running: %s, expected: %s',
723*9c5db199SXin Li                         running_rw_firmware_version, rw_version)
724*9c5db199SXin Li            logging.info('RO firmware, running: %s, expected: %s',
725*9c5db199SXin Li                         running_ro_firmware_version, ro_version)
726*9c5db199SXin Li
727*9c5db199SXin Li            return (running_rw_firmware_version == rw_version and
728*9c5db199SXin Li                    running_ro_firmware_version == ro_version)
729*9c5db199SXin Li        except:
730*9c5db199SXin Li            # We may not always be able to read the firmware version.
731*9c5db199SXin Li            # For example, if the firmware is erased due to RDP1, running any
732*9c5db199SXin Li            # commands (such as getting the version) won't work.
733*9c5db199SXin Li            return False
734*9c5db199SXin Li
735*9c5db199SXin Li    def is_rollback_set_to_initial_val(self):
736*9c5db199SXin Li        """
737*9c5db199SXin Li        Returns True if rollbackinfo matches the initial value that it
738*9c5db199SXin Li        should have coming from the factory.
739*9c5db199SXin Li        """
740*9c5db199SXin Li        return (self.get_rollback_id() ==
741*9c5db199SXin Li                self._ROLLBACK_INITIAL_BLOCK_ID
742*9c5db199SXin Li                and
743*9c5db199SXin Li                self.get_rollback_min_version() ==
744*9c5db199SXin Li                self._ROLLBACK_INITIAL_MIN_VERSION
745*9c5db199SXin Li                and
746*9c5db199SXin Li                self.get_rollback_rw_version() ==
747*9c5db199SXin Li                self._ROLLBACK_INITIAL_RW_VERSION)
748*9c5db199SXin Li
749*9c5db199SXin Li    def is_rollback_unset(self):
750*9c5db199SXin Li        """
751*9c5db199SXin Li        Returns True if rollbackinfo matches the uninitialized value that it
752*9c5db199SXin Li        should have after flashing the entire flash.
753*9c5db199SXin Li        """
754*9c5db199SXin Li        return (self.get_rollback_id() == self._ROLLBACK_ZERO_BLOCK_ID
755*9c5db199SXin Li                and self.get_rollback_min_version() ==
756*9c5db199SXin Li                self._ROLLBACK_INITIAL_MIN_VERSION
757*9c5db199SXin Li                and self.get_rollback_rw_version() ==
758*9c5db199SXin Li                self._ROLLBACK_INITIAL_RW_VERSION)
759*9c5db199SXin Li
760*9c5db199SXin Li    def biod_upstart_job_enabled(self):
761*9c5db199SXin Li        """Returns whether biod's upstart job file is at original location."""
762*9c5db199SXin Li        return self.host.is_file_exists(
763*9c5db199SXin Li                os.path.join(self._UPSTART_DIR, self._BIOD_UPSTART_JOB_FILE))
764*9c5db199SXin Li
765*9c5db199SXin Li    def disable_biod_upstart_job(self):
766*9c5db199SXin Li        """
767*9c5db199SXin Li        Disable biod's upstart job so that biod will not run after a reboot.
768*9c5db199SXin Li        """
769*9c5db199SXin Li        logging.info('Disabling biod\'s upstart job')
770*9c5db199SXin Li        filesystem_util.make_rootfs_writable(self.host)
771*9c5db199SXin Li        cmd = 'mv %s %s' % (os.path.join(
772*9c5db199SXin Li                self._UPSTART_DIR,
773*9c5db199SXin Li                self._BIOD_UPSTART_JOB_FILE), self._STATEFUL_PARTITION_DIR)
774*9c5db199SXin Li        result = self.run_cmd(cmd)
775*9c5db199SXin Li        if result.exit_status != 0:
776*9c5db199SXin Li            raise error.TestFail('Unable to disable biod upstart job: %s' %
777*9c5db199SXin Li                                 result.stderr.strip())
778*9c5db199SXin Li
779*9c5db199SXin Li    def enable_biod_upstart_job(self):
780*9c5db199SXin Li        """
781*9c5db199SXin Li        Enable biod's upstart job so that biod will run after a reboot.
782*9c5db199SXin Li        """
783*9c5db199SXin Li        logging.info('Enabling biod\'s upstart job')
784*9c5db199SXin Li        filesystem_util.make_rootfs_writable(self.host)
785*9c5db199SXin Li        cmd = 'mv %s %s' % (os.path.join(
786*9c5db199SXin Li                self._STATEFUL_PARTITION_DIR,
787*9c5db199SXin Li                self._BIOD_UPSTART_JOB_FILE), self._UPSTART_DIR)
788*9c5db199SXin Li        result = self.run_cmd(cmd)
789*9c5db199SXin Li        if result.exit_status != 0:
790*9c5db199SXin Li            raise error.TestFail('Unable to enable biod upstart job: %s' %
791*9c5db199SXin Li                                 result.stderr.strip())
792*9c5db199SXin Li
793*9c5db199SXin Li    def fp_updater_is_enabled(self):
794*9c5db199SXin Li        """Returns whether the fingerprint firmware updater is disabled."""
795*9c5db199SXin Li        return not self.host.is_file_exists(
796*9c5db199SXin Li                os.path.join(self._FINGERPRINT_BUILD_FW_DIR,
797*9c5db199SXin Li                             self._DISABLE_FP_UPDATER_FILE))
798*9c5db199SXin Li
799*9c5db199SXin Li    def disable_fp_updater(self):
800*9c5db199SXin Li        """Disable the fingerprint firmware updater."""
801*9c5db199SXin Li        filesystem_util.make_rootfs_writable(self.host)
802*9c5db199SXin Li        touch_cmd = 'touch %s' % os.path.join(self._FINGERPRINT_BUILD_FW_DIR,
803*9c5db199SXin Li                                              self._DISABLE_FP_UPDATER_FILE)
804*9c5db199SXin Li        logging.info('Disabling fp firmware updater')
805*9c5db199SXin Li        result = self.run_cmd(touch_cmd)
806*9c5db199SXin Li        if result.exit_status != 0:
807*9c5db199SXin Li            raise error.TestFail(
808*9c5db199SXin Li                    'Unable to write file to disable fp updater:'
809*9c5db199SXin Li                    ' command failed (rc=%s): %s' %
810*9c5db199SXin Li                    (result.exit_status, result.stderr.strip() or touch_cmd))
811*9c5db199SXin Li        self.run_cmd('sync')
812*9c5db199SXin Li
813*9c5db199SXin Li    def enable_fp_updater(self):
814*9c5db199SXin Li        """
815*9c5db199SXin Li        Enable the fingerprint firmware updater. Must be called only after
816*9c5db199SXin Li        disable_fp_updater().
817*9c5db199SXin Li        """
818*9c5db199SXin Li        filesystem_util.make_rootfs_writable(self.host)
819*9c5db199SXin Li        rm_cmd = 'rm %s' % os.path.join(self._FINGERPRINT_BUILD_FW_DIR,
820*9c5db199SXin Li                                        self._DISABLE_FP_UPDATER_FILE)
821*9c5db199SXin Li        logging.info('Enabling fp firmware updater')
822*9c5db199SXin Li        result = self.run_cmd(rm_cmd)
823*9c5db199SXin Li        if result.exit_status != 0:
824*9c5db199SXin Li            raise error.TestFail(
825*9c5db199SXin Li                    'Unable to rm .disable_fp_updater:'
826*9c5db199SXin Li                    ' command failed (rc=%s): %s' %
827*9c5db199SXin Li                    (result.exit_status, result.stderr.strip() or rm_cmd))
828*9c5db199SXin Li        self.run_cmd('sync')
829*9c5db199SXin Li
830*9c5db199SXin Li    def flash_rw_ro_firmware(self, fw_path):
831*9c5db199SXin Li        """Flashes *all* firmware (both RO and RW)."""
832*9c5db199SXin Li        # Check if FPMCU firmware needs to be re-flashed during cleanup
833*9c5db199SXin Li        self._need_fw_restore = True
834*9c5db199SXin Li        self.set_hardware_write_protect(False)
835*9c5db199SXin Li        flash_cmd = 'flash_fp_mcu' + ' --noservices ' + fw_path
836*9c5db199SXin Li        logging.info('Running flash cmd: %s', flash_cmd)
837*9c5db199SXin Li        flash_result = self.run_cmd(flash_cmd)
838*9c5db199SXin Li        self.set_hardware_write_protect(True)
839*9c5db199SXin Li
840*9c5db199SXin Li        # Zork cannot rebind cros-ec-uart after flashing, so an AP reboot is
841*9c5db199SXin Li        # needed to talk to FPMCU. See b/170213489.
842*9c5db199SXin Li        # We have to do this even if flashing failed.
843*9c5db199SXin Li        if hasattr(self, '_dut_needs_reboot') and self._dut_needs_reboot:
844*9c5db199SXin Li            self.host.reboot()
845*9c5db199SXin Li            if self.fp_updater_is_enabled():
846*9c5db199SXin Li                raise error.TestFail(
847*9c5db199SXin Li                        'Fp updater was not disabled when firmware is flashed')
848*9c5db199SXin Li            # If we just re-enable fp updater, it can still update (race
849*9c5db199SXin Li            # condition), so do it later in cleanup.
850*9c5db199SXin Li
851*9c5db199SXin Li        if flash_result.exit_status != 0:
852*9c5db199SXin Li            raise error.TestFail('Flashing RW/RO firmware failed')
853*9c5db199SXin Li
854*9c5db199SXin Li    def is_hardware_write_protect_enabled(self):
855*9c5db199SXin Li        """Returns state of hardware write protect."""
856*9c5db199SXin Li        fw_wp_state = self.servo.get('fw_wp_state')
857*9c5db199SXin Li        return fw_wp_state == 'on' or fw_wp_state == 'force_on'
858*9c5db199SXin Li
859*9c5db199SXin Li    def set_hardware_write_protect(self, enable):
860*9c5db199SXin Li        """Enables or disables hardware write protect."""
861*9c5db199SXin Li        self.servo.set('fw_wp_state', 'force_on' if enable else 'force_off')
862*9c5db199SXin Li
863*9c5db199SXin Li    def set_software_write_protect(self, enable):
864*9c5db199SXin Li        """Enables or disables software write protect."""
865*9c5db199SXin Li        arg  = 'enable' if enable else 'disable'
866*9c5db199SXin Li        self._run_ectool_cmd('flashprotect ' + arg)
867*9c5db199SXin Li        # TODO(b/116396469): The flashprotect command returns an error even on
868*9c5db199SXin Li        # success.
869*9c5db199SXin Li        # if result.exit_status != 0:
870*9c5db199SXin Li        #    raise error.TestFail('Failed to modify software write protect')
871*9c5db199SXin Li
872*9c5db199SXin Li        # TODO(b/116396469): "flashprotect enable" command is slow, so wait for
873*9c5db199SXin Li        # it to complete before attempting to reboot.
874*9c5db199SXin Li        time.sleep(2)
875*9c5db199SXin Li        self._reboot_ec()
876*9c5db199SXin Li
877*9c5db199SXin Li    def _reboot_ec(self):
878*9c5db199SXin Li        """Reboots the fingerprint MCU (FPMCU)."""
879*9c5db199SXin Li        self._run_ectool_cmd('reboot_ec')
880*9c5db199SXin Li        # TODO(b/116396469): The reboot_ec command returns an error even on
881*9c5db199SXin Li        # success.
882*9c5db199SXin Li        # if result.exit_status != 0:
883*9c5db199SXin Li        #    raise error.TestFail('Failed to reboot ec')
884*9c5db199SXin Li        time.sleep(2)
885*9c5db199SXin Li
886*9c5db199SXin Li    def get_files_from_dut(self, src, dst):
887*9c5db199SXin Li        """Copes files from DUT to server."""
888*9c5db199SXin Li        logging.info('Copying files from (%s) to (%s).', src, dst)
889*9c5db199SXin Li        self.host.get_file(src, dst, delete_dest=True)
890*9c5db199SXin Li
891*9c5db199SXin Li    def copy_files_to_dut(self, src_dir, dst_dir):
892*9c5db199SXin Li        """Copies files from server to DUT."""
893*9c5db199SXin Li        logging.info('Copying files from (%s) to (%s).', src_dir, dst_dir)
894*9c5db199SXin Li        self.host.send_file(src_dir, dst_dir, delete_dest=True)
895*9c5db199SXin Li        # Sync the filesystem in case we need to reboot the AP soon.
896*9c5db199SXin Li        self.run_cmd('sync')
897*9c5db199SXin Li
898*9c5db199SXin Li    def run_server_cmd(self, command, timeout=65):
899*9c5db199SXin Li        """Runs command on server; return result with output and exit code."""
900*9c5db199SXin Li        logging.info('Server execute: %s', command)
901*9c5db199SXin Li        result = utils.run(command, timeout=timeout, ignore_status=True)
902*9c5db199SXin Li        logging.info('exit_code: %d', result.exit_status)
903*9c5db199SXin Li        logging.info('stdout:\n%s', result.stdout)
904*9c5db199SXin Li        logging.info('stderr:\n%s', result.stderr)
905*9c5db199SXin Li        return result
906*9c5db199SXin Li
907*9c5db199SXin Li    def run_cmd(self, command, timeout=300):
908*9c5db199SXin Li        """Runs command on the DUT; return result with output and exit code."""
909*9c5db199SXin Li        logging.debug('DUT Execute: %s', command)
910*9c5db199SXin Li        result = self.host.run(command, timeout=timeout, ignore_status=True)
911*9c5db199SXin Li        logging.info('exit_code: %d', result.exit_status)
912*9c5db199SXin Li        logging.info('stdout:\n%s', result.stdout)
913*9c5db199SXin Li        logging.info('stderr:\n%s', result.stderr)
914*9c5db199SXin Li        return result
915*9c5db199SXin Li
916*9c5db199SXin Li    def _run_ectool_cmd(self, command):
917*9c5db199SXin Li        """Runs ectool on DUT; return result with output and exit code."""
918*9c5db199SXin Li        cmd = 'ectool ' + self._CROS_FP_ARG + ' ' + command
919*9c5db199SXin Li        result = self.run_cmd(cmd)
920*9c5db199SXin Li        return result
921*9c5db199SXin Li
922*9c5db199SXin Li    def _run_cros_config_cmd(self, command):
923*9c5db199SXin Li        """Runs cros_config on DUT; return result with output and exit code."""
924*9c5db199SXin Li        cmd = 'cros_config ' + self._CROS_CONFIG_FINGERPRINT_PATH + ' ' \
925*9c5db199SXin Li              + command
926*9c5db199SXin Li        result = self.run_cmd(cmd)
927*9c5db199SXin Li        return result
928*9c5db199SXin Li
929*9c5db199SXin Li    def _run_cros_config_cmd_cat(self, command):
930*9c5db199SXin Li        """Runs cat /run/chromeos-config/v1 on DUT; return result."""
931*9c5db199SXin Li        cmd = "cat /run/chromeos-config/v1/{}".format(command)
932*9c5db199SXin Li        return self.run_cmd(cmd)
933*9c5db199SXin Li
934*9c5db199SXin Li    def _run_dump_fmap_cmd(self, fw_file, section):
935*9c5db199SXin Li        """
936*9c5db199SXin Li        Runs "dump_fmap" on DUT for given file.
937*9c5db199SXin Li        Returns value of given section.
938*9c5db199SXin Li        """
939*9c5db199SXin Li        # Write result to stderr while redirecting stderr to stdout
940*9c5db199SXin Li        # and dropping stdout. This is done because dump_map only writes the
941*9c5db199SXin Li        # value read from a section to a file (will not just print it to
942*9c5db199SXin Li        # stdout).
943*9c5db199SXin Li        cmd = 'dump_fmap -x ' + fw_file + ' ' + section +\
944*9c5db199SXin Li              ':/dev/stderr /dev/stderr >& /dev/stdout > /dev/null'
945*9c5db199SXin Li        result = self.run_cmd(cmd)
946*9c5db199SXin Li        if result.exit_status != 0:
947*9c5db199SXin Li            raise error.TestFail('Failed to read section: %s' % section)
948*9c5db199SXin Li        return result.stdout.rstrip('\0')
949*9c5db199SXin Li
950*9c5db199SXin Li    def _run_futility_show_cmd(self, fw_file):
951*9c5db199SXin Li        """
952*9c5db199SXin Li        Runs "futility show" on DUT for given file.
953*9c5db199SXin Li        Returns stdout on success.
954*9c5db199SXin Li        """
955*9c5db199SXin Li        futility_cmd = 'futility show ' + fw_file
956*9c5db199SXin Li        result = self.run_cmd(futility_cmd)
957*9c5db199SXin Li        if result.exit_status != 0:
958*9c5db199SXin Li            raise error.TestFail('Unable to run futility on device')
959*9c5db199SXin Li        return result.stdout
960*9c5db199SXin Li
961*9c5db199SXin Li    def _run_sha256sum_cmd(self, file_name):
962*9c5db199SXin Li        """
963*9c5db199SXin Li        Runs "sha256sum" on DUT for given file.
964*9c5db199SXin Li        Returns stdout on success.
965*9c5db199SXin Li        """
966*9c5db199SXin Li        sha_cmd = 'sha256sum ' + file_name
967*9c5db199SXin Li        result = self.run_cmd(sha_cmd)
968*9c5db199SXin Li        if result.exit_status != 0:
969*9c5db199SXin Li            raise error.TestFail('Unable to calculate sha256sum on device')
970*9c5db199SXin Li        return result
971*9c5db199SXin Li
972*9c5db199SXin Li    def run_test(self, test_name, *args):
973*9c5db199SXin Li        """Runs test on DUT."""
974*9c5db199SXin Li        logging.info('Running %s', test_name)
975*9c5db199SXin Li        # Redirecting stderr to stdout since some commands intentionally fail
976*9c5db199SXin Li        # and it's easier to read when everything ordered in the same output
977*9c5db199SXin Li        test_cmd = ' '.join([os.path.join(self._dut_working_dir, test_name)] +
978*9c5db199SXin Li                            list(args) + ['2>&1'])
979*9c5db199SXin Li        # Change the working dir so we can write files from within the test
980*9c5db199SXin Li        # (otherwise defaults to $HOME (/root), which is not usually writable)
981*9c5db199SXin Li        # Note that dut_working_dir is automatically cleaned up so tests don't
982*9c5db199SXin Li        # need to worry about files from previous invocations or other tests.
983*9c5db199SXin Li        test_cmd = '(cd ' + self._dut_working_dir + ' && ' + test_cmd + ')'
984*9c5db199SXin Li        logging.info('Test command: %s', test_cmd)
985*9c5db199SXin Li        result = self.run_cmd(test_cmd)
986*9c5db199SXin Li        if result.exit_status != 0:
987*9c5db199SXin Li            raise error.TestFail(test_name + ' failed')
988