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