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