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