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