448 lines
18 KiB
Python
448 lines
18 KiB
Python
# Copyright 2017 The Chromium OS Authors. All rights reserved.
|
|
# Use of this source code is governed by a BSD-style license that can be
|
|
# found in the LICENSE file.
|
|
|
|
from __future__ import print_function
|
|
|
|
import logging
|
|
|
|
from autotest_lib.client.common_lib import error
|
|
from autotest_lib.client.common_lib.cros import cr50_utils
|
|
from autotest_lib.server.cros import filesystem_util
|
|
from autotest_lib.server.cros.faft.cr50_test import Cr50Test
|
|
|
|
|
|
class firmware_Cr50BID(Cr50Test):
|
|
"""Verify cr50 board id behavior on a board id locked image.
|
|
|
|
Check that cr50 will not accept mismatched board ids when it is running a
|
|
board id locked image.
|
|
|
|
Set the board id on a non board id locked image and verify cr50 will
|
|
rollback when it is updated to a mismatched board id image.
|
|
|
|
@param cr50_dbg_image_path: path to the node locked dev image.
|
|
"""
|
|
version = 1
|
|
|
|
MAX_BID = 0xffffffff
|
|
|
|
# The universal image can be run on any system no matter the board id.
|
|
UNIVERSAL = 'universal'
|
|
# The board id locked can only run on devices with the right chip board id.
|
|
BID_LOCKED = 'board_id_locked'
|
|
# Full support required for this test was added in different MP releases.
|
|
# - BID support was added in 0.0.21.
|
|
# - Keeping the rollback state after AP boot was added in 0.3.4.
|
|
# - Complete support for SPI PLT_RST straps was added in 0.3.18
|
|
# - 4us INT_AP_L pulse was added in 0.3.25
|
|
# - EC-EFS2 support was added in 0.5.4
|
|
# - 100us INT_AP_L pulse was added in 0.5.5 (Planned)
|
|
# TODO: use 5.5, so boards that require a 100us pulse can boot.
|
|
BID_SUPPORT = '0.5.4'
|
|
|
|
BID_MISMATCH = ['Board ID mismatched, but can not reboot.']
|
|
BID_ERROR = 5
|
|
SUCCESS = 0
|
|
|
|
# BID_BASE_TESTS is a list with the the board id and flags to test for each
|
|
# run. Each item in the list is a list of [board_id, flags, exit status].
|
|
# exit_status should be BID_ERROR if the board id and flags should not be
|
|
# compatible with the board id locked image.
|
|
#
|
|
# A image without board id will be able to run on a device with all of the
|
|
# board id and flag combinations.
|
|
#
|
|
# When using a non-symbolic board id, make sure the length of the string is
|
|
# greater than 4. If the string length is less than 4, usb_updater will
|
|
# treat it as a symbolic string
|
|
# ex: bid of 0 needs to be given as '0x0000'. If it were given as '0', the
|
|
# board id value would be interpreted as ord('0')
|
|
#
|
|
# These base tests are be true no matter the board id, mask, or flags. If a
|
|
# value is None, then it will be replaced with the test board id or flags
|
|
# while running the test.
|
|
BID_BASE_TESTS = [
|
|
[None, None, SUCCESS],
|
|
|
|
# Cr50 images are board id locked with flags. If we use 0 for the BID
|
|
# flags, there should be an error.
|
|
[None, 0, BID_ERROR],
|
|
|
|
# All 1s in the board id flags should be acceptable no matter the
|
|
# actual image flags
|
|
[None, MAX_BID, SUCCESS],
|
|
]
|
|
|
|
# Settings to test all of the cr50 BID responses. The dictionary conatins
|
|
# the name of the BID verification as the key and a list as a value.
|
|
#
|
|
# The value of the list is the image to start running the test with then
|
|
# the method to update to the board id locked image as the value.
|
|
#
|
|
# If the start image is 'board_id_locked', we won't try to update to the
|
|
# board id locked image.
|
|
BID_TEST_TYPE = [
|
|
# Verify that the board id locked image rejects invalid board ids
|
|
['get/set', BID_LOCKED],
|
|
|
|
# Verify the cr50 response when doing a normal update to a board id
|
|
# locked image. If there is a board id mismatch, cr50 should rollback
|
|
# to the image that was already running.
|
|
['rollback', UNIVERSAL],
|
|
|
|
# TODO (mruthven): add support for verifying recovery
|
|
# Certain devices are not able to successfully jump to the recovery
|
|
# image when the TPM is locked down. We need to find a way to verify the
|
|
# DUT is in recovery without being able to ssh into the DUT.
|
|
]
|
|
|
|
def initialize(self, host, cmdline_args, basic=False, full_args={}):
|
|
# Restore the original image and board id during cleanup.
|
|
super(firmware_Cr50BID, self).initialize(host, cmdline_args, full_args,
|
|
restore_cr50_image=True,
|
|
restore_cr50_board_id=True)
|
|
if self.servo.main_device_is_ccd():
|
|
raise error.TestNAError('Use a flex cable instead of CCD cable.')
|
|
|
|
if not self.cr50.has_command('bid'):
|
|
raise error.TestNAError('Cr50 image does not support board id')
|
|
|
|
self.image_versions = {}
|
|
|
|
self.save_board_id_locked_image()
|
|
self.save_universal_image()
|
|
|
|
# Add tests to the test list based on the running board id infomation
|
|
self.build_tests(basic)
|
|
|
|
|
|
def add_test(self, board_id, flags, expected_result):
|
|
"""Add a test case to the list of tests
|
|
|
|
The test will see if the board id locked image behaves as expected with
|
|
the given board_id and flags.
|
|
|
|
Args:
|
|
board_id: A symbolic string or hex str representing the board id.
|
|
flags: a int value for the flags
|
|
expected_result: SUCCESS if the board id and flags should be
|
|
accepted by the board id locked image. BID_ERROR if it should be
|
|
rejected.
|
|
"""
|
|
logging.info('Test Case: image board id %s with chip board id %s:%x '
|
|
'should %s', self.test_bid_str, board_id, flags,
|
|
'fail' if expected_result else 'succeed')
|
|
self.tests.append([board_id, flags, expected_result])
|
|
|
|
|
|
def add_board_id_tests(self):
|
|
"""Create a list of tests based on the board id and mask.
|
|
|
|
For each bit set to 1 in the board id image mask, Cr50 checks that the
|
|
bit in the board id infomask matches the image board id. Create a
|
|
couple of test cases based on the test mask and board id to verify this
|
|
behavior.
|
|
"""
|
|
mask_str = bin(self.test_mask).split('b')[1]
|
|
mask_str = '0' + mask_str if len(mask_str) < 32 else mask_str
|
|
mask_str = mask_str[::-1]
|
|
zero_index = mask_str.find('0')
|
|
one_index = mask_str.find('1')
|
|
|
|
# The hex version of the board id should be accepted.
|
|
self.add_test(hex(self.test_bid_int), self.test_flags, self.SUCCESS)
|
|
|
|
# Flip a bit we don't care about to make sure it is accepted
|
|
if zero_index != -1:
|
|
test_bid = self.test_bid_int ^ (1 << zero_index)
|
|
self.add_test(hex(test_bid), self.test_flags, self.SUCCESS)
|
|
|
|
|
|
if one_index != -1:
|
|
# Flip a bit we care about to make sure it is rejected
|
|
test_bid = self.test_bid_int ^ (1 << one_index)
|
|
self.add_test(hex(test_bid), self.test_flags, self.BID_ERROR)
|
|
else:
|
|
# If there is not a 1 in the board id mask, then we don't care about
|
|
# the board id at all. Flip all the bits and make sure setting the
|
|
# board id still succeeds.
|
|
test_bid = self.test_bid_int ^ self.MAX_BID
|
|
self.add_test(hex(test_bid), self.test_flags, self.SUCCESS)
|
|
|
|
|
|
def add_flag_tests(self):
|
|
"""Create a list of tests based on the test flags.
|
|
|
|
When comparing the flag field, cr50 makes sure all 1s set in the image
|
|
flags are also set as 1 in the infomask. Create a couple of test cases
|
|
to verify cr50 responds appropriately to different flags.
|
|
"""
|
|
flag_str = bin(self.test_flags).split('b')[1]
|
|
flag_str_pad = '0' + flag_str if len(flag_str) < 32 else flag_str
|
|
flag_str_pad_rev = flag_str_pad[::-1]
|
|
zero_index = flag_str_pad_rev.find('0')
|
|
one_index = flag_str_pad_rev.find('1')
|
|
|
|
# If we care about any flag bits, setting the flags to 0 should cause
|
|
# a rejection
|
|
if self.test_flags:
|
|
self.add_test(self.test_bid_sym, 0, self.BID_ERROR)
|
|
|
|
# Flip a 0 to 1 to make sure it is accepted.
|
|
if zero_index != -1:
|
|
test_flags = self.test_flags | (1 << zero_index)
|
|
self.add_test(self.test_bid_sym, test_flags, self.SUCCESS)
|
|
|
|
# Flip a 1 to 0 to make sure it is rejected.
|
|
if one_index != -1:
|
|
test_flags = self.test_flags ^ (1 << one_index)
|
|
self.add_test(self.test_bid_sym, test_flags, self.BID_ERROR)
|
|
|
|
|
|
def build_tests(self, basic):
|
|
"""Add more test cases based on the image board id, flags, and mask"""
|
|
self.tests = self.BID_BASE_TESTS
|
|
if not basic:
|
|
self.add_flag_tests()
|
|
self.add_board_id_tests()
|
|
logging.info('Running tests %r', self.tests)
|
|
|
|
|
|
def save_universal_image(self, rw_ver=BID_SUPPORT):
|
|
"""Get the non board id locked image
|
|
|
|
Save the universal image. Use the current cr50 image if it is not board
|
|
id locked. If the original image is board id locked, download a release
|
|
image from google storage.
|
|
|
|
Args:
|
|
rw_ver: The rw release version to use for the universal image.
|
|
"""
|
|
release_info = self.download_cr50_release_image(rw_ver)
|
|
self.universal_path, universal_ver = release_info
|
|
|
|
logging.info('Running test with universal image %s', universal_ver)
|
|
|
|
self.replace_image_if_newer(universal_ver[1], cr50_utils.CR50_PROD)
|
|
self.replace_image_if_newer(universal_ver[1], cr50_utils.CR50_PREPVT)
|
|
|
|
self.image_versions[self.UNIVERSAL] = universal_ver
|
|
|
|
|
|
def replace_image_if_newer(self, universal_rw_ver, path):
|
|
"""Replace the image at path if it is newer than the universal image
|
|
|
|
Copy the universal image to path, if the universal image is older than
|
|
the image at path.
|
|
|
|
Args:
|
|
universal_rw_ver: The rw version string of the universal image
|
|
path: The path of the image that may need to be replaced.
|
|
"""
|
|
if self.host.path_exists(path):
|
|
dut_ver = cr50_utils.GetBinVersion(self.host, path)[1]
|
|
# If the universal version is lower than the DUT image, install the
|
|
# universal image. It has the lowest version of any image in the
|
|
# test, so cr50-update won't try to update cr50 at any point during
|
|
# the test.
|
|
install_image = (cr50_utils.GetNewestVersion(dut_ver,
|
|
universal_rw_ver) == dut_ver)
|
|
else:
|
|
# If the DUT doesn't have a file at path, install the image.
|
|
install_image = True
|
|
|
|
if install_image:
|
|
# Disable rootfs verification so we can copy the image to the DUT
|
|
filesystem_util.make_rootfs_writable(self.host)
|
|
# Copy the universal image onto the DUT.
|
|
dest, ver = cr50_utils.InstallImage(self.host, self.universal_path,
|
|
path)
|
|
logging.info('Copied %s to %s', ver, dest)
|
|
|
|
|
|
def save_board_id_locked_image(self):
|
|
"""Save the running image and get the board id information.
|
|
|
|
Save the board id locked image. If the running image isn't board id
|
|
locked, the test will be skipped.
|
|
|
|
Raises:
|
|
TestNAError if the running cr50 image is not board id locked.
|
|
"""
|
|
version = self.get_saved_cr50_original_version()
|
|
if not version[2]:
|
|
raise error.TestNAError('The cr50 image is not board id locked')
|
|
|
|
self.board_id_locked_path = self.get_saved_cr50_original_path()
|
|
|
|
image_bid_info = cr50_utils.GetBoardIdInfoTuple(version[2])
|
|
self.test_bid_int, self.test_mask, self.test_flags = image_bid_info
|
|
self.test_bid_sym = cr50_utils.GetSymbolicBoardId(self.test_bid_int)
|
|
self.test_bid_str = cr50_utils.GetBoardIdInfoString(version[2])
|
|
|
|
if not self.test_flags:
|
|
raise error.TestNAError('Image needs to have non-zero flags to run '
|
|
'test')
|
|
logging.info('Running test with bid locked image %s', version)
|
|
self.image_versions[self.BID_LOCKED] = version
|
|
|
|
|
|
def is_running_version(self, rw_ver, bid_str):
|
|
"""Returns True if the running image has the same rw ver and bid
|
|
|
|
Args:
|
|
rw_ver: rw version string
|
|
bid_str: A symbolic or non-smybolic board id
|
|
|
|
Returns:
|
|
True if cr50 is running an image with the given rw version and
|
|
board id.
|
|
"""
|
|
running_rw = self.cr50.get_version()
|
|
running_bid = self.cr50.get_active_board_id_str()
|
|
# Convert the image board id to a non symbolic board id
|
|
bid_str = cr50_utils.GetBoardIdInfoString(bid_str, symbolic=False)
|
|
return running_rw == rw_ver and bid_str == running_bid
|
|
|
|
|
|
def reset_state(self, image_type):
|
|
"""Update to the image and erase the board id.
|
|
|
|
We can't erase the board id unless we are running a debug image. Update
|
|
to the debug image so we can erase the board id and then rollback to the
|
|
right image.
|
|
|
|
Args:
|
|
image_type: the name of the image we want to be running at the end
|
|
of reset_state: 'universal' or 'board_id_locked'. This
|
|
image name needs to correspond with some test attribute
|
|
${image_type}_path
|
|
|
|
Raises:
|
|
TestFail if the board id was not erased
|
|
"""
|
|
_, rw_ver, bid = self.image_versions[image_type]
|
|
chip_bid = cr50_utils.GetChipBoardId(self.host)
|
|
if self.is_running_version(rw_ver, bid) and (chip_bid ==
|
|
cr50_utils.ERASED_CHIP_BID):
|
|
logging.info('Skipping reset. Already running %s image with erased '
|
|
'chip board id', image_type)
|
|
return
|
|
logging.info('Updating to %s image and erasing chip bid', image_type)
|
|
|
|
self.eraseflashinfo_and_restore_image(self.get_saved_dbg_image_path())
|
|
|
|
self.cr50_update(getattr(self, image_type + '_path'), rollback=True)
|
|
|
|
# Verify the board id was erased
|
|
if cr50_utils.GetChipBoardId(self.host) != cr50_utils.ERASED_CHIP_BID:
|
|
raise error.TestFail('Could not erase bid')
|
|
|
|
|
|
def updater_set_bid(self, bid, flags, exit_code):
|
|
"""Set the flags using usb_updater and verify the result
|
|
|
|
Args:
|
|
board_id: board id string
|
|
flags: An int with the flag value
|
|
exit_code: the expected error code. 0 if it should succeed
|
|
|
|
Raises:
|
|
TestFail if usb_updater had an unexpected exit status or setting the
|
|
board id failed
|
|
"""
|
|
|
|
original_bid, _, original_flags = cr50_utils.GetChipBoardId(self.host)
|
|
|
|
if exit_code:
|
|
exit_code = 'Error %d while setting board id' % exit_code
|
|
|
|
try:
|
|
cr50_utils.SetChipBoardId(self.host, bid, flags)
|
|
result = self.SUCCESS
|
|
except error.AutoservRunError as e:
|
|
result = e.result_obj.stderr.strip()
|
|
|
|
if result != exit_code:
|
|
raise error.TestFail("Unexpected result setting %s:%x expected "
|
|
"'%s' got '%s'" %
|
|
(bid, flags, exit_code, result))
|
|
|
|
# Verify cr50 is still running with the same board id and flags
|
|
if exit_code:
|
|
cr50_utils.CheckChipBoardId(self.host, original_bid, original_flags)
|
|
|
|
|
|
def run_bid_test(self, image_name, bid, flags, bid_error):
|
|
"""Set the bid and flags. Verify a board id locked image response
|
|
|
|
Update to the right image type and try to set the board id. Only the
|
|
board id locked image should reject the given board id and flags.
|
|
|
|
If we are setting the board id on a non-board id locked image, try to
|
|
update to the board id locked image afterwards to verify that cr50 does
|
|
or doesn't rollback. If there is a bid error, cr50 should fail to update
|
|
to the board id locked image.
|
|
|
|
|
|
Args:
|
|
image_name: The image name 'universal', 'dev', or 'board_id_locked'
|
|
bid: A string representing the board id. Either the hex or symbolic
|
|
value
|
|
flags: A int value for the flags to set
|
|
bid_error: The expected usb_update error code. 0 for success 5 for
|
|
failure
|
|
"""
|
|
is_bid_locked_image = image_name == self.BID_LOCKED
|
|
|
|
# If the image is not board id locked, it should accept any board id and
|
|
# flags
|
|
exit_code = bid_error if is_bid_locked_image else self.SUCCESS
|
|
|
|
response = 'error %d' % exit_code if exit_code else 'success'
|
|
logging.info('EXPECT %s setting bid to %s:%x with %s image',
|
|
response, bid, flags, image_name)
|
|
|
|
# Erase the chip board id and update to the correct image
|
|
self.reset_state(image_name)
|
|
|
|
# Try to set the board id and flags
|
|
self.updater_set_bid(bid, flags, exit_code)
|
|
|
|
# If it failed before, it should fail with the same error. If we already
|
|
# set the board id, it should fail because the board id is already set.
|
|
self.updater_set_bid(bid, flags, exit_code if exit_code else 7)
|
|
|
|
# After setting the board id with a non boardid locked image, try to
|
|
# update to the board id locked image. Verify that cr50 does/doesn't run
|
|
# it. If there is a mismatch, the update should fail and Cr50 should
|
|
# rollback to the universal image.
|
|
if not is_bid_locked_image:
|
|
self.cr50_update(self.board_id_locked_path,
|
|
expect_rollback=(not not bid_error))
|
|
|
|
|
|
def run_once(self):
|
|
"""Verify the Cr50 BID response of each test bid."""
|
|
errors = []
|
|
for test_type, image_name in self.BID_TEST_TYPE:
|
|
logging.info('VERIFY: BID %s', test_type)
|
|
for i, args in enumerate(self.tests):
|
|
bid, flags, bid_error = args
|
|
# Replace place holder values with the test values
|
|
bid = bid if bid != None else self.test_bid_sym
|
|
flags = flags if flags != None else self.test_flags
|
|
message = '%s %d %s:%x %s' % (test_type, i, bid, flags,
|
|
bid_error)
|
|
|
|
# Run the test with the given bid, flags, and result
|
|
try:
|
|
self.run_bid_test(image_name, bid, flags, bid_error)
|
|
logging.info('Verified %s', message)
|
|
except (error.TestFail, error.TestError) as e:
|
|
logging.info('FAILED %s with "%s"', message, e)
|
|
errors.append('%s with "%s"' % (message, e))
|
|
if len(errors):
|
|
raise error.TestFail('failed tests: %s' % errors)
|