• Home
  • Line#
  • Scopes#
  • Navigate#
  • Raw
  • Download
1# Copyright 2017 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
5from __future__ import print_function
6
7import logging
8
9from autotest_lib.client.common_lib import error
10from autotest_lib.client.common_lib.cros import cr50_utils
11from autotest_lib.server.cros.faft.cr50_test import Cr50Test
12
13
14class firmware_Cr50BID(Cr50Test):
15    """Verify cr50 board id behavior on a board id locked image.
16
17    Check that cr50 will not accept mismatched board ids when it is running a
18    board id locked image.
19
20    Set the board id on a non board id locked image and verify cr50 will
21    rollback when it is updated to a mismatched board id image.
22
23    @param cr50_dbg_image_path: path to the node locked dev image.
24    """
25    version = 1
26
27    MAX_BID = 0xffffffff
28
29    # The universal image can be run on any system no matter the board id.
30    UNIVERSAL = 'universal'
31    # The board id locked can only run on devices with the right chip board id.
32    BID_LOCKED = 'board_id_locked'
33    # Full support required for this test was added in different MP releases.
34    # - BID support was added in 0.0.21.
35    # - Keeping the rollback state after AP boot was added in 0.3.4.
36    # - Complete support for SPI PLT_RST straps was added in 0.3.18
37    # - 4us INT_AP_L pulse was added in 0.3.25
38    # - EC-EFS2 support was added in 0.5.4
39    # - 100us INT_AP_L pulse was added in 0.5.5
40    # - third rollback bit blown 0.5.20
41    BID_SUPPORT = '0.5.20'
42
43    BID_MISMATCH = ['Board ID mismatched, but can not reboot.']
44    BID_ERROR = 5
45    SUCCESS = 0
46
47    # BID_BASE_TESTS is a list with the the board id and flags to test for each
48    # run. Each item in the list is a list of [board_id, flags, exit status].
49    # exit_status should be BID_ERROR if the board id and flags should not be
50    # compatible with the board id locked image.
51    #
52    # A image without board id will be able to run on a device with all of the
53    # board id and flag combinations.
54    #
55    # When using a non-symbolic board id, make sure the length of the string is
56    # greater than 4. If the string length is less than 4, usb_updater will
57    # treat it as a symbolic string
58    # ex: bid of 0 needs to be given as '0x0000'. If it were given as '0', the
59    # board id value would be interpreted as ord('0')
60    #
61    # These base tests are be true no matter the board id, mask, or flags. If a
62    # value is None, then it will be replaced with the test board id or flags
63    # while running the test.
64    BID_BASE_TESTS = [
65        [None, None, SUCCESS],
66
67        # Cr50 images are board id locked with flags. If we use 0 for the BID
68        # flags, there should be an error.
69        [None, 0, BID_ERROR],
70
71        # All 1s in the board id flags should be acceptable no matter the
72        # actual image flags
73        [None, MAX_BID, SUCCESS],
74    ]
75
76    # Settings to test all of the cr50 BID responses. The dictionary conatins
77    # the name of the BID verification as the key and a list as a value.
78    #
79    # The value of the list is the image to start running the test with then
80    # the method to update to the board id locked image as the value.
81    #
82    # If the start image is 'board_id_locked', we won't try to update to the
83    # board id locked image.
84    BID_TEST_TYPE = [
85        # Verify that the board id locked image rejects invalid board ids
86        ['get/set', BID_LOCKED],
87
88        # Verify the cr50 response when doing a normal update to a board id
89        # locked image. If there is a board id mismatch, cr50 should rollback
90        # to the image that was already running.
91        ['rollback', UNIVERSAL],
92
93        # TODO (mruthven): add support for verifying recovery
94        # Certain devices are not able to successfully jump to the recovery
95        # image when the TPM is locked down. We need to find a way to verify the
96        # DUT is in recovery without being able to ssh into the DUT.
97    ]
98
99    def initialize(self, host, cmdline_args, basic=False, full_args={}):
100        # Restore the original image and board id during cleanup.
101        super(firmware_Cr50BID, self).initialize(host, cmdline_args, full_args,
102                                                 restore_cr50_image=True,
103                                                 restore_cr50_board_id=True)
104        if self.servo.main_device_is_ccd():
105            raise error.TestNAError('Use a flex cable instead of CCD cable.')
106
107        if not self.cr50.has_command('bid'):
108            raise error.TestNAError('Cr50 image does not support board id')
109
110        self.image_versions = {}
111
112        self.save_board_id_locked_image()
113        self.save_universal_image()
114
115        # Add tests to the test list based on the running board id infomation
116        self.build_tests(basic)
117
118
119    def add_test(self, board_id, flags, expected_result):
120        """Add a test case to the list of tests
121
122        The test will see if the board id locked image behaves as expected with
123        the given board_id and flags.
124
125        Args:
126            board_id: A symbolic string or hex str representing the board id.
127            flags: a int value for the flags
128            expected_result: SUCCESS if the board id and flags should be
129                accepted by the board id locked image. BID_ERROR if it should be
130                rejected.
131        """
132        logging.info('Test Case: image board id %s with chip board id %s:%x '
133                     'should %s', self.test_bid_str, board_id, flags,
134                     'fail' if expected_result else 'succeed')
135        self.tests.append([board_id, flags, expected_result])
136
137
138    def add_board_id_tests(self):
139        """Create a list of tests based on the board id and mask.
140
141        For each bit set to 1 in the board id image mask, Cr50 checks that the
142        bit in the board id infomask matches the image board id. Create a
143        couple of test cases based on the test mask and board id to verify this
144        behavior.
145        """
146        mask_str = bin(self.test_mask).split('b')[1]
147        mask_str = '0' + mask_str if len(mask_str) < 32 else mask_str
148        mask_str = mask_str[::-1]
149        zero_index = mask_str.find('0')
150        one_index = mask_str.find('1')
151
152        # The hex version of the board id should be accepted.
153        self.add_test(hex(self.test_bid_int), self.test_flags, self.SUCCESS)
154
155        # Flip a bit we don't care about to make sure it is accepted
156        if zero_index != -1:
157            test_bid = self.test_bid_int ^ (1 << zero_index)
158            self.add_test(hex(test_bid), self.test_flags, self.SUCCESS)
159
160
161        if one_index != -1:
162            # Flip a bit we care about to make sure it is rejected
163            test_bid = self.test_bid_int ^ (1 << one_index)
164            self.add_test(hex(test_bid), self.test_flags, self.BID_ERROR)
165        else:
166            # If there is not a 1 in the board id mask, then we don't care about
167            # the board id at all. Flip all the bits and make sure setting the
168            # board id still succeeds.
169            test_bid = self.test_bid_int ^ self.MAX_BID
170            self.add_test(hex(test_bid), self.test_flags, self.SUCCESS)
171
172
173    def add_flag_tests(self):
174        """Create a list of tests based on the test flags.
175
176        When comparing the flag field, cr50 makes sure all 1s set in the image
177        flags are also set as 1 in the infomask. Create a couple of test cases
178        to verify cr50 responds appropriately to different flags.
179        """
180        flag_str = bin(self.test_flags).split('b')[1]
181        flag_str_pad = '0' + flag_str if len(flag_str) < 32 else flag_str
182        flag_str_pad_rev = flag_str_pad[::-1]
183        zero_index = flag_str_pad_rev.find('0')
184        one_index = flag_str_pad_rev.find('1')
185
186        # If we care about any flag bits, setting the flags to 0 should cause
187        # a rejection
188        if self.test_flags:
189            self.add_test(self.test_bid_sym, 0, self.BID_ERROR)
190
191        # Flip a 0 to 1 to make sure it is accepted.
192        if zero_index != -1:
193            test_flags = self.test_flags | (1 << zero_index)
194            self.add_test(self.test_bid_sym, test_flags, self.SUCCESS)
195
196        # Flip a 1 to 0 to make sure it is rejected.
197        if one_index != -1:
198            test_flags = self.test_flags ^ (1 << one_index)
199            self.add_test(self.test_bid_sym, test_flags, self.BID_ERROR)
200
201
202    def build_tests(self, basic):
203        """Add more test cases based on the image board id, flags, and mask"""
204        self.tests = self.BID_BASE_TESTS
205        if not basic:
206            self.add_flag_tests()
207            self.add_board_id_tests()
208        logging.info('Running tests %r', self.tests)
209
210
211    def save_universal_image(self, rw_ver=BID_SUPPORT):
212        """Get the non board id locked image
213
214        Save the universal image. Use the current cr50 image if it is not board
215        id locked. If the original image is board id locked, download a release
216        image from google storage.
217
218        Args:
219            rw_ver: The rw release version to use for the universal image.
220        """
221        release_info = self.download_cr50_release_image(rw_ver)
222        self.universal_path, universal_ver = release_info
223
224        logging.info('Running test with universal image %s', universal_ver)
225
226        self.replace_image_if_newer(universal_ver[1], cr50_utils.CR50_PROD)
227        self.replace_image_if_newer(universal_ver[1], cr50_utils.CR50_PREPVT)
228
229        self.image_versions[self.UNIVERSAL] = universal_ver
230
231
232    def replace_image_if_newer(self, universal_rw_ver, path):
233        """Replace the image at path if it is newer than the universal image
234
235        Copy the universal image to path, if the universal image is older than
236        the image at path.
237
238        Args:
239            universal_rw_ver: The rw version string of the universal image
240            path: The path of the image that may need to be replaced.
241        """
242        if self.host.path_exists(path):
243            dut_ver = cr50_utils.GetBinVersion(self.host, path)[1]
244            # If the universal version is lower than the DUT image, install the
245            # universal image. It has the lowest version of any image in the
246            # test, so cr50-update won't try to update cr50 at any point during
247            # the test.
248            install_image = (cr50_utils.GetNewestVersion(dut_ver,
249                    universal_rw_ver) == dut_ver)
250        else:
251            # If the DUT doesn't have a file at path, install the image.
252            install_image = True
253
254        if install_image:
255            # Disable rootfs verification so we can copy the image to the DUT
256            self.make_rootfs_writable()
257            # Copy the universal image onto the DUT.
258            dest, ver = cr50_utils.InstallImage(self.host, self.universal_path,
259                    path)
260            logging.info('Copied %s to %s', ver, dest)
261
262
263    def save_board_id_locked_image(self):
264        """Save the running image and get the board id information.
265
266        Save the board id locked image. If the running image isn't board id
267        locked, the test will be skipped.
268
269        Raises:
270            TestNAError if the running cr50 image is not board id locked.
271        """
272        version = self.get_saved_cr50_original_version()
273        if not version[2]:
274            raise error.TestNAError('The cr50 image is not board id locked')
275
276        self.board_id_locked_path = self.get_saved_cr50_original_path()
277
278        image_bid_info = cr50_utils.GetBoardIdInfoTuple(version[2])
279        self.test_bid_int, self.test_mask, self.test_flags = image_bid_info
280        self.test_bid_sym = cr50_utils.GetSymbolicBoardId(self.test_bid_int)
281        self.test_bid_str = cr50_utils.GetBoardIdInfoString(version[2])
282
283        if not self.test_flags:
284            raise error.TestNAError('Image needs to have non-zero flags to run '
285                                    'test')
286        logging.info('Running test with bid locked image %s', version)
287        self.image_versions[self.BID_LOCKED] = version
288
289
290    def is_running_version(self, rw_ver, bid_str):
291        """Returns True if the running image has the same rw ver and bid
292
293        Args:
294            rw_ver: rw version string
295            bid_str: A symbolic or non-smybolic board id
296
297        Returns:
298            True if cr50 is running an image with the given rw version and
299            board id.
300        """
301        running_rw = self.cr50.get_version()
302        running_bid = self.cr50.get_active_board_id_str()
303        # Convert the image board id to a non symbolic board id
304        bid_str = cr50_utils.GetBoardIdInfoString(bid_str, symbolic=False)
305        return running_rw == rw_ver and bid_str == running_bid
306
307
308    def reset_state(self, image_type):
309        """Update to the image and erase the board id.
310
311        We can't erase the board id unless we are running a debug image. Update
312        to the debug image so we can erase the board id and then rollback to the
313        right image.
314
315        Args:
316            image_type: the name of the image we want to be running at the end
317                        of reset_state: 'universal' or 'board_id_locked'. This
318                        image name needs to correspond with some test attribute
319                        ${image_type}_path
320
321        Raises:
322            TestFail if the board id was not erased
323        """
324        _, rw_ver, bid = self.image_versions[image_type]
325        chip_bid = cr50_utils.GetChipBoardId(self.host)
326        if self.is_running_version(rw_ver, bid) and (chip_bid ==
327            cr50_utils.ERASED_CHIP_BID):
328            logging.info('Skipping reset. Already running %s image with erased '
329                'chip board id', image_type)
330            return
331        logging.info('Updating to %s image and erasing chip bid', image_type)
332
333        self.eraseflashinfo_and_restore_image(self.get_saved_dbg_image_path())
334
335        self.cr50_update(getattr(self, image_type + '_path'), rollback=True)
336
337        # Verify the board id was erased
338        if cr50_utils.GetChipBoardId(self.host) != cr50_utils.ERASED_CHIP_BID:
339            raise error.TestFail('Could not erase bid')
340
341
342    def updater_set_bid(self, bid, flags, exit_code):
343        """Set the flags using usb_updater and verify the result
344
345        Args:
346            board_id: board id string
347            flags: An int with the flag value
348            exit_code: the expected error code. 0 if it should succeed
349
350        Raises:
351            TestFail if usb_updater had an unexpected exit status or setting the
352            board id failed
353        """
354
355        original_bid, _, original_flags = cr50_utils.GetChipBoardId(self.host)
356
357        if exit_code:
358            exit_code = 'Error %d while setting board id' % exit_code
359
360        try:
361            cr50_utils.SetChipBoardId(self.host, bid, flags)
362            result = self.SUCCESS
363        except error.AutoservRunError as e:
364            result = e.result_obj.stderr.strip()
365
366        if result != exit_code:
367            raise error.TestFail("Unexpected result setting %s:%x expected "
368                                 "'%s' got '%s'" %
369                                 (bid, flags, exit_code, result))
370
371        # Verify cr50 is still running with the same board id and flags
372        if exit_code:
373            cr50_utils.CheckChipBoardId(self.host, original_bid, original_flags)
374
375
376    def run_bid_test(self, image_name, bid, flags, bid_error):
377        """Set the bid and flags. Verify a board id locked image response
378
379        Update to the right image type and try to set the board id. Only the
380        board id locked image should reject the given board id and flags.
381
382        If we are setting the board id on a non-board id locked image, try to
383        update to the board id locked image afterwards to verify that cr50 does
384        or doesn't rollback. If there is a bid error, cr50 should fail to update
385        to the board id locked image.
386
387
388        Args:
389            image_name: The image name 'universal', 'dev', or 'board_id_locked'
390            bid: A string representing the board id. Either the hex or symbolic
391                 value
392            flags: A int value for the flags to set
393            bid_error: The expected usb_update error code. 0 for success 5 for
394                       failure
395        """
396        is_bid_locked_image = image_name == self.BID_LOCKED
397
398        # If the image is not board id locked, it should accept any board id and
399        # flags
400        exit_code = bid_error if is_bid_locked_image else self.SUCCESS
401
402        response = 'error %d' % exit_code if exit_code else 'success'
403        logging.info('EXPECT %s setting bid to %s:%x with %s image',
404                     response, bid, flags, image_name)
405
406        # Erase the chip board id and update to the correct image
407        self.reset_state(image_name)
408
409        # Try to set the board id and flags
410        self.updater_set_bid(bid, flags, exit_code)
411
412        # If it failed before, it should fail with the same error. If we already
413        # set the board id, it should fail because the board id is already set.
414        self.updater_set_bid(bid, flags, exit_code if exit_code else 7)
415
416        # After setting the board id with a non boardid locked image, try to
417        # update to the board id locked image. Verify that cr50 does/doesn't run
418        # it. If there is a mismatch, the update should fail and Cr50 should
419        # rollback to the universal image.
420        if not is_bid_locked_image:
421            self.cr50_update(self.board_id_locked_path,
422                             expect_rollback=(not not bid_error))
423
424
425    def run_once(self):
426        """Verify the Cr50 BID response of each test bid."""
427        errors = []
428        for test_type, image_name in self.BID_TEST_TYPE:
429            logging.info('VERIFY: BID %s', test_type)
430            for i, args in enumerate(self.tests):
431                bid, flags, bid_error = args
432                # Replace place holder values with the test values
433                bid = bid if bid != None else self.test_bid_sym
434                flags = flags if flags != None else self.test_flags
435                message = '%s %d %s:%x %s' % (test_type, i, bid, flags,
436                    bid_error)
437
438                # Run the test with the given bid, flags, and result
439                try:
440                    self.run_bid_test(image_name, bid, flags, bid_error)
441                    logging.info('Verified %s', message)
442                except (error.TestFail, error.TestError) as e:
443                    logging.info('FAILED %s with "%s"', message, e)
444                    errors.append('%s with "%s"' % (message, e))
445        if len(errors):
446            raise error.TestFail('failed tests: %s' % errors)
447