1# Copyright 2018 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 6import os 7import time 8 9from autotest_lib.server import test 10from autotest_lib.server.cros import filesystem_util 11from autotest_lib.client.common_lib import error, utils 12 13 14class FingerprintTest(test.test): 15 """Base class that sets up helpers for fingerprint tests.""" 16 version = 1 17 18 # Location of firmware from the build on the DUT 19 _FINGERPRINT_BUILD_FW_DIR = '/opt/google/biod/fw' 20 21 _DISABLE_FP_UPDATER_FILE = '.disable_fp_updater' 22 23 _UPSTART_DIR = '/etc/init' 24 _BIOD_UPSTART_JOB_FILE = 'biod.conf' 25 _STATEFUL_PARTITION_DIR = '/mnt/stateful_partition' 26 27 _GENIMAGES_SCRIPT_NAME = 'gen_test_images.sh' 28 _GENIMAGES_OUTPUT_DIR_NAME = 'images' 29 30 _TEST_IMAGE_FORMAT_MAP = { 31 'TEST_IMAGE_ORIGINAL': '%s.bin', 32 'TEST_IMAGE_DEV': '%s.dev', 33 'TEST_IMAGE_CORRUPT_FIRST_BYTE': '%s_corrupt_first_byte.bin', 34 'TEST_IMAGE_CORRUPT_LAST_BYTE': '%s_corrupt_last_byte.bin', 35 'TEST_IMAGE_DEV_RB_ZERO': '%s.dev.rb0', 36 'TEST_IMAGE_DEV_RB_ONE': '%s.dev.rb1', 37 'TEST_IMAGE_DEV_RB_NINE': '%s.dev.rb9' 38 } 39 40 _ROLLBACK_ZERO_BLOCK_ID = '0' 41 _ROLLBACK_INITIAL_BLOCK_ID = '1' 42 _ROLLBACK_INITIAL_MIN_VERSION = '0' 43 _ROLLBACK_INITIAL_RW_VERSION = '0' 44 45 _SERVER_GENERATED_FW_DIR_NAME = 'generated_fw' 46 47 _DUT_TMP_PATH_BASE = '/tmp/fp_test' 48 49 # Name of key in "futility show" output corresponds to the signing key ID 50 _FUTILITY_KEY_ID_KEY_NAME = 'ID' 51 52 # Types of firmware 53 _FIRMWARE_TYPE_RO = 'RO' 54 _FIRMWARE_TYPE_RW = 'RW' 55 56 # Types of signing keys 57 _KEY_TYPE_DEV = 'dev' 58 _KEY_TYPE_PRE_MP = 'premp' 59 _KEY_TYPE_MP = 'mp' 60 61 # EC board names for FPMCUs 62 _FP_BOARD_NAME_BLOONCHIPPER = 'bloonchipper' 63 _FP_BOARD_NAME_DARTMONKEY = 'dartmonkey' 64 _FP_BOARD_NAME_NOCTURNE = 'nocturne_fp' 65 _FP_BOARD_NAME_NAMI = 'nami_fp' 66 67 # Map from signing key ID to type of signing key 68 _KEY_ID_MAP_ = { 69 # bloonchipper 70 '61382804da86b4156d666cc9a976088f8b647d44': _KEY_TYPE_DEV, 71 '07b1af57220c196e363e68d73a5966047c77011e': _KEY_TYPE_PRE_MP, 72 '1c590ef36399f6a2b2ef87079c135b69ef89eb60': _KEY_TYPE_MP, 73 74 # dartmonkey 75 '257a0aa3ac9e81aa4bc3aabdb6d3d079117c5799': _KEY_TYPE_MP, 76 77 # nocturne 78 '8a8fc039a9463271995392f079b83ce33832d07d': _KEY_TYPE_DEV, 79 '6f38c866182bd9bf7a4462c06ac04fa6a0074351': _KEY_TYPE_MP, 80 'f6f7d96c48bd154dbae7e3fe3a3b4c6268a10934': _KEY_TYPE_PRE_MP, 81 82 # nami 83 '754aea623d69975a22998f7b97315dd53115d723': _KEY_TYPE_PRE_MP, 84 '35486c0090ca390408f1fbbf2a182966084fe2f8': _KEY_TYPE_MP 85 86 } 87 88 # RO versions that are flashed in the factory 89 # (for eternity for a given board) 90 _GOLDEN_RO_FIRMWARE_VERSION_MAP = { 91 _FP_BOARD_NAME_BLOONCHIPPER: { 92 'hatch': 'bloonchipper_v2.0.4277-9f652bb3', 93 'zork': 'bloonchipper_v2.0.5938-197506c1', 94 }, 95 _FP_BOARD_NAME_DARTMONKEY: 'dartmonkey_v2.0.2887-311310808', 96 _FP_BOARD_NAME_NOCTURNE: 'nocturne_fp_v2.2.64-58cf5974e', 97 _FP_BOARD_NAME_NAMI: 'nami_fp_v2.2.144-7a08e07eb', 98 } 99 100 _FIRMWARE_VERSION_SHA256SUM = 'sha256sum' 101 _FIRMWARE_VERSION_RO_VERSION = 'ro_version' 102 _FIRMWARE_VERSION_RW_VERSION = 'rw_version' 103 _FIRMWARE_VERSION_KEY_ID = 'key_id' 104 105 # Map of attributes for a given board's various firmware file releases 106 # 107 # Two purposes: 108 # 1) Documents the exact versions and keys used for a given firmware file. 109 # 2) Used to verify that files that end up in the build (and therefore 110 # what we release) is exactly what we expect. 111 _FIRMWARE_VERSION_MAP = { 112 _FP_BOARD_NAME_BLOONCHIPPER: { 113 'bloonchipper_v2.0.4277-9f652bb3.bin': { 114 _FIRMWARE_VERSION_SHA256SUM: '7d9b788a908bee5c83e27450258b2bbf110d7253d49faa4804562ae27e42cb3b', 115 _FIRMWARE_VERSION_RO_VERSION: 'bloonchipper_v2.0.4277-9f652bb3', 116 _FIRMWARE_VERSION_RW_VERSION: 'bloonchipper_v2.0.4277-9f652bb3', 117 _FIRMWARE_VERSION_KEY_ID: '1c590ef36399f6a2b2ef87079c135b69ef89eb60', 118 }, 119 'bloonchipper_v2.0.5938-197506c1.bin': { 120 _FIRMWARE_VERSION_SHA256SUM: 'dc62e4b05eaf4fa8ab5546dcf18abdb30c8e64e9bf0fbf377ebc85155c7c3a47', 121 _FIRMWARE_VERSION_RO_VERSION: 'bloonchipper_v2.0.5938-197506c1', 122 _FIRMWARE_VERSION_RW_VERSION: 'bloonchipper_v2.0.5938-197506c1', 123 _FIRMWARE_VERSION_KEY_ID: '1c590ef36399f6a2b2ef87079c135b69ef89eb60', 124 }, 125 }, 126 _FP_BOARD_NAME_NOCTURNE: { 127 'nocturne_fp_v2.2.64-58cf5974e-RO_v2.0.4017-9c45fb4b3-RW.bin': { 128 _FIRMWARE_VERSION_SHA256SUM: '16c405eeaff75dcbc76dbc9f368f66e3fabc47e2ebcf13bd2b64b8b133bbff97', 129 _FIRMWARE_VERSION_RO_VERSION: 'nocturne_fp_v2.2.64-58cf5974e', 130 _FIRMWARE_VERSION_RW_VERSION: 'nocturne_fp_v2.0.4017-9c45fb4b3', 131 _FIRMWARE_VERSION_KEY_ID: '6f38c866182bd9bf7a4462c06ac04fa6a0074351', 132 }, 133 }, 134 _FP_BOARD_NAME_NAMI: { 135 'nami_fp_v2.2.144-7a08e07eb-RO_v2.0.4017-9c45fb4b3-RW.bin': { 136 _FIRMWARE_VERSION_SHA256SUM: '7965ea4c4371ee6d21dc462b9ed7c99078d17f4b772bec51441ca9af7d8f3a80', 137 _FIRMWARE_VERSION_RO_VERSION: 'nami_fp_v2.2.144-7a08e07eb', 138 _FIRMWARE_VERSION_RW_VERSION: 'nami_fp_v2.0.4017-9c45fb4b3', 139 _FIRMWARE_VERSION_KEY_ID: '35486c0090ca390408f1fbbf2a182966084fe2f8', 140 }, 141 }, 142 _FP_BOARD_NAME_DARTMONKEY: { 143 'dartmonkey_v2.0.2887-311310808-RO_v2.0.4017-9c45fb4b3-RW.bin': { 144 _FIRMWARE_VERSION_SHA256SUM: 'b84914c70e93c28e2221f48be338dbf0ad0cfb12b7877baaf6b47f7bfd2aa958', 145 _FIRMWARE_VERSION_RO_VERSION: 'dartmonkey_v2.0.2887-311310808', 146 _FIRMWARE_VERSION_RW_VERSION: 'dartmonkey_v2.0.4017-9c45fb4b3', 147 _FIRMWARE_VERSION_KEY_ID: '257a0aa3ac9e81aa4bc3aabdb6d3d079117c5799', 148 } 149 } 150 } 151 152 _BIOD_UPSTART_JOB_NAME = 'biod' 153 # TODO(crbug.com/925545) 154 _TIMBERSLIDE_UPSTART_JOB_NAME = \ 155 'timberslide LOG_PATH=/sys/kernel/debug/cros_fp/console_log' 156 157 _INIT_ENTROPY_CMD = 'bio_wash --factory_init' 158 159 _CROS_FP_ARG = '--name=cros_fp' 160 _CROS_CONFIG_FINGERPRINT_PATH = '/fingerprint' 161 _ECTOOL_RO_VERSION = 'RO version' 162 _ECTOOL_RW_VERSION = 'RW version' 163 _ECTOOL_FIRMWARE_COPY = 'Firmware copy' 164 _ECTOOL_ROLLBACK_BLOCK_ID = 'Rollback block id' 165 _ECTOOL_ROLLBACK_MIN_VERSION = 'Rollback min version' 166 _ECTOOL_ROLLBACK_RW_VERSION = 'RW rollback version' 167 168 @staticmethod 169 def _parse_colon_delimited_output(ectool_output): 170 """ 171 Converts ectool's (or any other tool with similar output) colon 172 delimited output into python dict. Ignores any lines that do not have 173 colons. 174 175 Example: 176 RO version: nocturne_fp_v2.2.64-58cf5974e 177 RW version: nocturne_fp_v2.2.110-b936c0a3c 178 179 becomes: 180 { 181 'RO version': 'nocturne_fp_v2.2.64-58cf5974e', 182 'RW version': 'nocturne_fp_v2.2.110-b936c0a3c' 183 } 184 """ 185 ret = {} 186 try: 187 for line in ectool_output.strip().split('\n'): 188 splits = line.split(':', 1) 189 if len(splits) != 2: 190 continue 191 key = splits[0].strip() 192 val = splits[1].strip() 193 ret[key] = val 194 except: 195 raise error.TestFail('Unable to parse ectool output: %s' 196 % ectool_output) 197 return ret 198 199 def initialize(self, host): 200 """Perform minimal initialization, to avoid AttributeError in cleanup""" 201 self.host = host 202 self.servo = host.servo 203 204 self._validate_compatible_servo_version() 205 206 self.servo.initialize_dut() 207 208 self.fp_board = self.get_fp_board() 209 self._build_fw_file = self.get_build_fw_file() 210 211 def setup_test(self, test_dir, use_dev_signed_fw=False, 212 enable_hardware_write_protect=True, 213 enable_software_write_protect=True, 214 force_firmware_flashing=False, init_entropy=True): 215 """Perform more complete initialization, including copying test files""" 216 logging.info('HW write protect enabled: %s', 217 self.is_hardware_write_protect_enabled()) 218 219 # TODO(crbug.com/925545): stop timberslide so /var/log/cros_fp.log 220 # continues to update after flashing. 221 self._timberslide_running = self.host.upstart_status( 222 self._TIMBERSLIDE_UPSTART_JOB_NAME) 223 if self._timberslide_running: 224 logging.info('Stopping %s', self._TIMBERSLIDE_UPSTART_JOB_NAME) 225 self.host.upstart_stop(self._TIMBERSLIDE_UPSTART_JOB_NAME) 226 227 self._biod_running = self.host.upstart_status( 228 self._BIOD_UPSTART_JOB_NAME) 229 if self._biod_running: 230 logging.info('Stopping %s', self._BIOD_UPSTART_JOB_NAME) 231 self.host.upstart_stop(self._BIOD_UPSTART_JOB_NAME) 232 233 # On some platforms an AP reboot is needed after flashing firmware to 234 # rebind the driver. 235 self._dut_needs_reboot = self.get_host_board() == 'zork' 236 237 if filesystem_util.is_rootfs_writable(self.host): 238 if self._dut_needs_reboot: 239 logging.warning('rootfs is writable') 240 else: 241 raise error.TestFail('rootfs is writable') 242 243 if not self.biod_upstart_job_enabled(): 244 raise error.TestFail( 245 'Biod upstart job is disabled at the beginning of test') 246 if not self.fp_updater_is_enabled(): 247 raise error.TestFail( 248 'Fingerprint firmware updater is disabled at the beginning of test' 249 ) 250 251 # Disable biod and updater so that they won't interfere after reboot. 252 if self._dut_needs_reboot: 253 self.disable_biod_upstart_job() 254 self.disable_fp_updater() 255 256 # create tmp working directory on device (automatically cleaned up) 257 self._dut_working_dir = self.host.get_tmp_dir( 258 parent=self._DUT_TMP_PATH_BASE) 259 logging.info('Created dut_working_dir: %s', self._dut_working_dir) 260 self.copy_files_to_dut(test_dir, self._dut_working_dir) 261 262 self.validate_build_fw_file() 263 264 gen_script = os.path.abspath(os.path.join(self.autodir, 265 'server', 'cros', 'faft', 266 self._GENIMAGES_SCRIPT_NAME)) 267 self._dut_firmware_test_images_dir = \ 268 self._generate_test_firmware_images(gen_script, 269 self._build_fw_file, 270 self._dut_working_dir) 271 logging.info('dut_firmware_test_images_dir: %s', 272 self._dut_firmware_test_images_dir) 273 274 self._initialize_test_firmware_image_attrs( 275 self._dut_firmware_test_images_dir) 276 277 self._initialize_running_fw_version(use_dev_signed_fw, 278 force_firmware_flashing) 279 280 if init_entropy: 281 self._initialize_fw_entropy() 282 283 self._initialize_hw_and_sw_write_protect(enable_hardware_write_protect, 284 enable_software_write_protect) 285 286 def cleanup(self): 287 """Restores original state.""" 288 # Once the tests complete we need to make sure we're running the 289 # original firmware (not dev version) and potentially reset rollback. 290 self._initialize_running_fw_version(use_dev_signed_fw=False, 291 force_firmware_flashing=False) 292 self._initialize_fw_entropy() 293 # Re-enable biod and updater after flashing and initializing entropy so 294 # that they don't interfere if there was a reboot. 295 if hasattr(self, '_dut_needs_reboot') and self._dut_needs_reboot: 296 if not self.biod_upstart_job_enabled(): 297 self.enable_biod_upstart_job() 298 if not self.fp_updater_is_enabled(): 299 self.enable_fp_updater() 300 self._initialize_hw_and_sw_write_protect( 301 enable_hardware_write_protect=True, 302 enable_software_write_protect=True) 303 if hasattr(self, '_biod_running') and self._biod_running: 304 logging.info('Restarting biod') 305 self.host.upstart_restart(self._BIOD_UPSTART_JOB_NAME) 306 # TODO(crbug.com/925545) 307 if hasattr(self, '_timberslide_running') and self._timberslide_running: 308 logging.info('Restarting timberslide') 309 self.host.upstart_restart(self._TIMBERSLIDE_UPSTART_JOB_NAME) 310 311 super(FingerprintTest, self).cleanup() 312 313 def after_run_once(self): 314 """Logs which iteration just ran.""" 315 logging.info('successfully ran iteration %d', self.iteration) 316 317 def _validate_compatible_servo_version(self): 318 """Asserts if a compatible servo version is not attached.""" 319 servo_version = self.servo.get_servo_version() 320 logging.info('servo version: %s', servo_version) 321 322 def _generate_test_firmware_images(self, gen_script, build_fw_file, 323 dut_working_dir): 324 """ 325 Copies the fingerprint firmware from the DUT to the server running 326 the tests, which runs a script to generate various test versions of 327 the firmware. 328 329 @return full path to location of test images on DUT 330 """ 331 # create subdirectory under existing tmp dir 332 server_tmp_dir = os.path.join(self.tmpdir, 333 self._SERVER_GENERATED_FW_DIR_NAME) 334 os.mkdir(server_tmp_dir) 335 logging.info('server_tmp_dir: %s', server_tmp_dir) 336 337 # Copy firmware from device to server 338 self.get_files_from_dut(build_fw_file, server_tmp_dir) 339 340 # Run the test image generation script on server 341 pushd = os.getcwd() 342 os.chdir(server_tmp_dir) 343 cmd = ' '.join([gen_script, 344 self.get_fp_board(), 345 os.path.basename(build_fw_file)]) 346 result = self.run_server_cmd(cmd) 347 if result.exit_status != 0: 348 raise error.TestFail('Failed to run test image generation script') 349 350 os.chdir(pushd) 351 352 # Copy resulting files to DUT tmp dir 353 server_generated_images_dir = \ 354 os.path.join(server_tmp_dir, self._GENIMAGES_OUTPUT_DIR_NAME) 355 self.copy_files_to_dut(server_generated_images_dir, dut_working_dir) 356 357 return os.path.join(dut_working_dir, self._GENIMAGES_OUTPUT_DIR_NAME) 358 359 def _initialize_test_firmware_image_attrs(self, dut_fw_test_images_dir): 360 """Sets attributes with full path to test images on DUT. 361 362 Example: self.TEST_IMAGE_DEV = /some/path/images/nocturne_fp.dev 363 """ 364 for key, val in self._TEST_IMAGE_FORMAT_MAP.iteritems(): 365 full_path = os.path.join(dut_fw_test_images_dir, 366 val % self.get_fp_board()) 367 setattr(self, key, full_path) 368 369 def _initialize_running_fw_version(self, use_dev_signed_fw, 370 force_firmware_flashing): 371 """ 372 Ensures that the running firmware version matches build version 373 and factory rollback settings; flashes to correct version if either 374 fails to match is requested to force flashing. 375 376 RO firmware: original version released at factory 377 RW firmware: firmware from current build 378 """ 379 build_rw_firmware_version = \ 380 self.get_build_rw_firmware_version(use_dev_signed_fw) 381 golden_ro_firmware_version = \ 382 self.get_golden_ro_firmware_version(use_dev_signed_fw) 383 logging.info('Build RW firmware version: %s', build_rw_firmware_version) 384 logging.info('Golden RO firmware version: %s', 385 golden_ro_firmware_version) 386 387 running_rw_firmware = self.ensure_running_rw_firmware() 388 389 fw_versions_match = self.running_fw_version_matches_given_version( 390 build_rw_firmware_version, golden_ro_firmware_version) 391 392 if not running_rw_firmware or not fw_versions_match \ 393 or not self.is_rollback_set_to_initial_val() \ 394 or force_firmware_flashing: 395 fw_file = self._build_fw_file 396 if use_dev_signed_fw: 397 fw_file = self.TEST_IMAGE_DEV 398 self.flash_rw_ro_firmware(fw_file) 399 if not self.running_fw_version_matches_given_version( 400 build_rw_firmware_version, golden_ro_firmware_version): 401 raise error.TestFail( 402 'Running firmware version does not match expected version') 403 404 def _initialize_fw_entropy(self): 405 """Sets the entropy (key) in FPMCU flash (if not set).""" 406 result = self.run_cmd(self._INIT_ENTROPY_CMD) 407 if result.exit_status != 0: 408 raise error.TestFail('Unable to initialize entropy') 409 410 def _initialize_hw_and_sw_write_protect(self, enable_hardware_write_protect, 411 enable_software_write_protect): 412 """Enables/disables hardware/software write protect.""" 413 # sw: 0, hw: 0 => initial_hw(0) -> sw(0) -> hw(0) 414 # sw: 0, hw: 1 => initial_hw(0) -> sw(0) -> hw(1) 415 # sw: 1, hw: 0 => initial_hw(1) -> sw(1) -> hw(0) 416 # sw: 1, hw: 1 => initial_hw(1) -> sw(1) -> hw(1) 417 hardware_write_protect_initial_enabled = True 418 if not enable_software_write_protect: 419 hardware_write_protect_initial_enabled = False 420 421 self.set_hardware_write_protect(hardware_write_protect_initial_enabled) 422 423 self.set_software_write_protect(enable_software_write_protect) 424 self.set_hardware_write_protect(enable_hardware_write_protect) 425 426 def get_fp_board(self): 427 """Returns name of fingerprint EC. 428 429 nocturne and nami are special cases and have "_fp" appended. Newer 430 FPMCUs have unique names. 431 See go/cros-fingerprint-firmware-branching-and-signing. 432 """ 433 # Use cros_config to get fingerprint board. 434 # Due to b/160271883, we will try running the cmd via cat instead. 435 result = self._run_cros_config_cmd_cat('fingerprint/board') 436 if result.exit_status != 0: 437 raise error.TestFail( 438 'Unable to get fingerprint board with cros_config') 439 return result.stdout.rstrip() 440 441 def get_host_board(self): 442 """Returns name of the host board.""" 443 return self.host.get_board().split(':')[-1] 444 445 def get_build_fw_file(self): 446 """Returns full path to build FW file on DUT.""" 447 ls_cmd = 'ls %s/%s*.bin' % ( 448 self._FINGERPRINT_BUILD_FW_DIR, self.fp_board) 449 result = self.run_cmd(ls_cmd) 450 if result.exit_status != 0: 451 raise error.TestFail( 452 'Unable to find firmware file on device:' 453 ' command failed (rc=%s): %s' 454 % (result.exit_status, result.stderr.strip() or ls_cmd)) 455 ret = result.stdout.rstrip() 456 logging.info('Build firmware file: %s', ret) 457 return ret 458 459 def check_equal(self, a, b): 460 """Raises exception if "a" does not equal "b".""" 461 if a != b: 462 raise error.TestFail('"%s" does not match expected "%s" for board ' 463 '%s' % (a, b, self.get_fp_board())) 464 465 def validate_build_fw_file(self, 466 allowed_types=(_KEY_TYPE_PRE_MP, _KEY_TYPE_MP)): 467 """ 468 Checks that all attributes in the given firmware file match their 469 expected values. 470 471 @param allowed_types: If key type is something else, raise TestFail. 472 Default: pre-MP or MP. 473 @type allowed_types: tuple | list 474 """ 475 build_fw_file = self._build_fw_file 476 # check hash 477 actual_hash = self._calculate_sha256sum(build_fw_file) 478 expected_hash = self._get_expected_firmware_hash(build_fw_file) 479 self.check_equal(actual_hash, expected_hash) 480 481 # check signing key_id 482 actual_key_id = self._read_firmware_key_id(build_fw_file) 483 expected_key_id = self._get_expected_firmware_key_id(build_fw_file) 484 self.check_equal(actual_key_id, expected_key_id) 485 486 # check that the signing key for firmware in the build 487 # is "pre mass production" (pre-mp) or "mass production" (MP) 488 key_type = self._get_key_type(actual_key_id) 489 if key_type not in allowed_types: 490 raise error.TestFail( 491 'Firmware key type must be %s for board %s; got %s (%s)' % 492 (' or '.join(allowed_types), self.fp_board, key_type, 493 actual_key_id)) 494 495 # check ro_version 496 actual_ro_version = self._read_firmware_ro_version(build_fw_file) 497 expected_ro_version = \ 498 self._get_expected_firmware_ro_version(build_fw_file) 499 self.check_equal(actual_ro_version, expected_ro_version) 500 501 # check rw_version 502 actual_rw_version = self._read_firmware_rw_version(build_fw_file) 503 expected_rw_version = \ 504 self._get_expected_firmware_rw_version(build_fw_file) 505 self.check_equal(actual_rw_version, expected_rw_version) 506 507 logging.info("Validated build firmware metadata.") 508 509 def _get_key_type(self, key_id): 510 """Returns the key "type" for a given "key id".""" 511 key_type = self._KEY_ID_MAP_.get(key_id) 512 if key_type is None: 513 raise error.TestFail('Unable to get key type for key id: %s' 514 % key_id) 515 return key_type 516 517 def _get_expected_firmware_info(self, build_fw_file, info_type): 518 """ 519 Returns expected firmware info for a given firmware file name. 520 """ 521 build_fw_file_name = os.path.basename(build_fw_file) 522 523 board = self.get_fp_board() 524 board_expected_fw_info = self._FIRMWARE_VERSION_MAP.get(board) 525 if board_expected_fw_info is None: 526 raise error.TestFail('Unable to get firmware info for board: %s' 527 % board) 528 529 expected_fw_info = board_expected_fw_info.get(build_fw_file_name) 530 if expected_fw_info is None: 531 raise error.TestFail('Unable to get firmware info for file: %s' 532 % build_fw_file_name) 533 534 ret = expected_fw_info.get(info_type) 535 if ret is None: 536 raise error.TestFail('Unable to get firmware info type: %s' 537 % info_type) 538 539 return ret 540 541 def _get_expected_firmware_hash(self, build_fw_file): 542 """Returns expected hash of firmware file.""" 543 return self._get_expected_firmware_info( 544 build_fw_file, self._FIRMWARE_VERSION_SHA256SUM) 545 546 def _get_expected_firmware_key_id(self, build_fw_file): 547 """Returns expected "key id" for firmware file.""" 548 return self._get_expected_firmware_info( 549 build_fw_file, self._FIRMWARE_VERSION_KEY_ID) 550 551 def _get_expected_firmware_ro_version(self, build_fw_file): 552 """Returns expected RO version for firmware file.""" 553 return self._get_expected_firmware_info( 554 build_fw_file, self._FIRMWARE_VERSION_RO_VERSION) 555 556 def _get_expected_firmware_rw_version(self, build_fw_file): 557 """Returns expected RW version for firmware file.""" 558 return self._get_expected_firmware_info( 559 build_fw_file, self._FIRMWARE_VERSION_RW_VERSION) 560 561 def _read_firmware_key_id(self, file_name): 562 """Returns "key id" as read from the given file.""" 563 result = self._run_futility_show_cmd(file_name) 564 parsed = self._parse_colon_delimited_output(result) 565 key_id = parsed.get(self._FUTILITY_KEY_ID_KEY_NAME) 566 if key_id is None: 567 raise error.TestFail('Failed to get key ID for file: %s' 568 % file_name) 569 return key_id 570 571 def _read_firmware_ro_version(self, file_name): 572 """Returns RO firmware version as read from the given file.""" 573 return self._run_dump_fmap_cmd(file_name, 'RO_FRID') 574 575 def _read_firmware_rw_version(self, file_name): 576 """Returns RW firmware version as read from the given file.""" 577 return self._run_dump_fmap_cmd(file_name, 'RW_FWID') 578 579 def _calculate_sha256sum(self, file_name): 580 """Returns SHA256 hash of the given file contents.""" 581 result = self._run_sha256sum_cmd(file_name) 582 return result.stdout.split()[0] 583 584 def _get_running_firmware_info(self, key): 585 """ 586 Returns requested firmware info (RW version, RO version, or firmware 587 type). 588 """ 589 result = self._run_ectool_cmd('version') 590 parsed = self._parse_colon_delimited_output(result.stdout) 591 if result.exit_status != 0: 592 raise error.TestFail('Failed to get running firmware info') 593 info = parsed.get(key) 594 if info is None: 595 raise error.TestFail( 596 'Failed to get running firmware info: %s' % key) 597 return info 598 599 def get_running_rw_firmware_version(self): 600 """Returns running RW firmware version.""" 601 return self._get_running_firmware_info(self._ECTOOL_RW_VERSION) 602 603 def get_running_ro_firmware_version(self): 604 """Returns running RO firmware version.""" 605 return self._get_running_firmware_info(self._ECTOOL_RO_VERSION) 606 607 def get_running_firmware_type(self): 608 """Returns type of firmware we are running (RW or RO).""" 609 return self._get_running_firmware_info(self._ECTOOL_FIRMWARE_COPY) 610 611 def _get_rollback_info(self, info_type): 612 """Returns requested type of rollback info.""" 613 result = self._run_ectool_cmd('rollbackinfo') 614 parsed = self._parse_colon_delimited_output(result.stdout) 615 if result.exit_status != 0: 616 raise error.TestFail('Failed to get rollback info') 617 info = parsed.get(info_type) 618 if info is None: 619 raise error.TestFail('Failed to get rollback info: %s' % info_type) 620 return info 621 622 def get_rollback_id(self): 623 """Returns rollback ID.""" 624 return self._get_rollback_info(self._ECTOOL_ROLLBACK_BLOCK_ID) 625 626 def get_rollback_min_version(self): 627 """Returns rollback min version.""" 628 return self._get_rollback_info(self._ECTOOL_ROLLBACK_MIN_VERSION) 629 630 def get_rollback_rw_version(self): 631 """Returns RW rollback version.""" 632 return self._get_rollback_info(self._ECTOOL_ROLLBACK_RW_VERSION) 633 634 def _construct_dev_version(self, orig_version): 635 """ 636 Given a "regular" version string from a signed build, returns the 637 special "dev" version that we use when creating the test images. 638 """ 639 fw_version = orig_version 640 if len(fw_version) + len('.dev') > 31: 641 fw_version = fw_version[:27] 642 fw_version = fw_version + '.dev' 643 return fw_version 644 645 def get_golden_ro_firmware_version(self, use_dev_signed_fw): 646 """Returns RO firmware version used in factory.""" 647 board = self.get_fp_board() 648 golden_version = self._GOLDEN_RO_FIRMWARE_VERSION_MAP.get(board) 649 if isinstance(golden_version, dict): 650 golden_version = golden_version.get(self.get_host_board()) 651 if golden_version is None: 652 raise error.TestFail('Unable to get golden RO version for board: %s' 653 % board) 654 if use_dev_signed_fw: 655 golden_version = self._construct_dev_version(golden_version) 656 return golden_version 657 658 def get_build_rw_firmware_version(self, use_dev_signed_fw): 659 """Returns RW firmware version from build.""" 660 fw_version = self._read_firmware_rw_version(self._build_fw_file) 661 if use_dev_signed_fw: 662 fw_version = self._construct_dev_version(fw_version) 663 return fw_version 664 665 def ensure_running_rw_firmware(self): 666 """ 667 Check whether the device is running RW firmware. If not, try rebooting 668 to RW. 669 670 @return true if successfully verified running RW firmware, false 671 otherwise. 672 """ 673 try: 674 if self.get_running_firmware_type() != self._FIRMWARE_TYPE_RW: 675 self._reboot_ec() 676 if self.get_running_firmware_type() != self._FIRMWARE_TYPE_RW: 677 # RW may be corrupted. 678 return False 679 except: 680 # We may not always be able to read the firmware version. 681 # For example, if the firmware is erased due to RDP1, running any 682 # commands (such as getting the version) won't work. 683 return False 684 return True 685 686 def running_fw_version_matches_given_version(self, rw_version, ro_version): 687 """ 688 Returns True if the running RO and RW firmware versions match the 689 provided versions. 690 """ 691 try: 692 running_rw_firmware_version = self.get_running_rw_firmware_version() 693 running_ro_firmware_version = self.get_running_ro_firmware_version() 694 695 logging.info('RW firmware, running: %s, expected: %s', 696 running_rw_firmware_version, rw_version) 697 logging.info('RO firmware, running: %s, expected: %s', 698 running_ro_firmware_version, ro_version) 699 700 return (running_rw_firmware_version == rw_version and 701 running_ro_firmware_version == ro_version) 702 except: 703 # We may not always be able to read the firmware version. 704 # For example, if the firmware is erased due to RDP1, running any 705 # commands (such as getting the version) won't work. 706 return False 707 708 def is_rollback_set_to_initial_val(self): 709 """ 710 Returns True if rollbackinfo matches the initial value that it 711 should have coming from the factory. 712 """ 713 return (self.get_rollback_id() == 714 self._ROLLBACK_INITIAL_BLOCK_ID 715 and 716 self.get_rollback_min_version() == 717 self._ROLLBACK_INITIAL_MIN_VERSION 718 and 719 self.get_rollback_rw_version() == 720 self._ROLLBACK_INITIAL_RW_VERSION) 721 722 def is_rollback_unset(self): 723 """ 724 Returns True if rollbackinfo matches the uninitialized value that it 725 should have after flashing the entire flash. 726 """ 727 return (self.get_rollback_id() == self._ROLLBACK_ZERO_BLOCK_ID 728 and self.get_rollback_min_version() == 729 self._ROLLBACK_INITIAL_MIN_VERSION 730 and self.get_rollback_rw_version() == 731 self._ROLLBACK_INITIAL_RW_VERSION) 732 733 def biod_upstart_job_enabled(self): 734 """Returns whether biod's upstart job file is at original location.""" 735 return self.host.is_file_exists( 736 os.path.join(self._UPSTART_DIR, self._BIOD_UPSTART_JOB_FILE)) 737 738 def disable_biod_upstart_job(self): 739 """ 740 Disable biod's upstart job so that biod will not run after a reboot. 741 """ 742 logging.info('Disabling biod\'s upstart job') 743 filesystem_util.make_rootfs_writable(self.host) 744 cmd = 'mv %s %s' % (os.path.join( 745 self._UPSTART_DIR, 746 self._BIOD_UPSTART_JOB_FILE), self._STATEFUL_PARTITION_DIR) 747 result = self.run_cmd(cmd) 748 if result.exit_status != 0: 749 raise error.TestFail('Unable to disable biod upstart job: %s' % 750 result.stderr.strip()) 751 752 def enable_biod_upstart_job(self): 753 """ 754 Enable biod's upstart job so that biod will run after a reboot. 755 """ 756 logging.info('Enabling biod\'s upstart job') 757 filesystem_util.make_rootfs_writable(self.host) 758 cmd = 'mv %s %s' % (os.path.join( 759 self._STATEFUL_PARTITION_DIR, 760 self._BIOD_UPSTART_JOB_FILE), self._UPSTART_DIR) 761 result = self.run_cmd(cmd) 762 if result.exit_status != 0: 763 raise error.TestFail('Unable to enable biod upstart job: %s' % 764 result.stderr.strip()) 765 766 def fp_updater_is_enabled(self): 767 """Returns whether the fingerprint firmware updater is disabled.""" 768 return not self.host.is_file_exists( 769 os.path.join(self._FINGERPRINT_BUILD_FW_DIR, 770 self._DISABLE_FP_UPDATER_FILE)) 771 772 def disable_fp_updater(self): 773 """Disable the fingerprint firmware updater.""" 774 filesystem_util.make_rootfs_writable(self.host) 775 touch_cmd = 'touch %s' % os.path.join(self._FINGERPRINT_BUILD_FW_DIR, 776 self._DISABLE_FP_UPDATER_FILE) 777 logging.info('Disabling fp firmware updater') 778 result = self.run_cmd(touch_cmd) 779 if result.exit_status != 0: 780 raise error.TestFail( 781 'Unable to write file to disable fp updater:' 782 ' command failed (rc=%s): %s' % 783 (result.exit_status, result.stderr.strip() or touch_cmd)) 784 self.run_cmd('sync') 785 786 def enable_fp_updater(self): 787 """ 788 Enable the fingerprint firmware updater. Must be called only after 789 disable_fp_updater(). 790 """ 791 filesystem_util.make_rootfs_writable(self.host) 792 rm_cmd = 'rm %s' % os.path.join(self._FINGERPRINT_BUILD_FW_DIR, 793 self._DISABLE_FP_UPDATER_FILE) 794 logging.info('Enabling fp firmware updater') 795 result = self.run_cmd(rm_cmd) 796 if result.exit_status != 0: 797 raise error.TestFail( 798 'Unable to rm .disable_fp_updater:' 799 ' command failed (rc=%s): %s' % 800 (result.exit_status, result.stderr.strip() or rm_cmd)) 801 self.run_cmd('sync') 802 803 def flash_rw_ro_firmware(self, fw_path): 804 """Flashes *all* firmware (both RO and RW).""" 805 self.set_hardware_write_protect(False) 806 flash_cmd = 'flash_fp_mcu' + ' ' + fw_path 807 logging.info('Running flash cmd: %s', flash_cmd) 808 flash_result = self.run_cmd(flash_cmd) 809 self.set_hardware_write_protect(True) 810 811 # Zork cannot rebind cros-ec-uart after flashing, so an AP reboot is 812 # needed to talk to FPMCU. See b/170213489. 813 # We have to do this even if flashing failed. 814 if hasattr(self, '_dut_needs_reboot') and self._dut_needs_reboot: 815 self.host.reboot() 816 if self.fp_updater_is_enabled(): 817 raise error.TestFail( 818 'Fp updater was not disabled when firmware is flashed') 819 # If we just re-enable fp updater, it can still update (race 820 # condition), so do it later in cleanup. 821 822 if flash_result.exit_status != 0: 823 raise error.TestFail('Flashing RW/RO firmware failed') 824 825 def is_hardware_write_protect_enabled(self): 826 """Returns state of hardware write protect.""" 827 fw_wp_state = self.servo.get('fw_wp_state') 828 return fw_wp_state == 'on' or fw_wp_state == 'force_on' 829 830 def set_hardware_write_protect(self, enable): 831 """Enables or disables hardware write protect.""" 832 self.servo.set('fw_wp_state', 'force_on' if enable else 'force_off') 833 834 def set_software_write_protect(self, enable): 835 """Enables or disables software write protect.""" 836 arg = 'enable' if enable else 'disable' 837 self._run_ectool_cmd('flashprotect ' + arg) 838 # TODO(b/116396469): The flashprotect command returns an error even on 839 # success. 840 # if result.exit_status != 0: 841 # raise error.TestFail('Failed to modify software write protect') 842 843 # TODO(b/116396469): "flashprotect enable" command is slow, so wait for 844 # it to complete before attempting to reboot. 845 time.sleep(2) 846 self._reboot_ec() 847 848 def _reboot_ec(self): 849 """Reboots the fingerprint MCU (FPMCU).""" 850 self._run_ectool_cmd('reboot_ec') 851 # TODO(b/116396469): The reboot_ec command returns an error even on 852 # success. 853 # if result.exit_status != 0: 854 # raise error.TestFail('Failed to reboot ec') 855 time.sleep(2) 856 857 def get_files_from_dut(self, src, dst): 858 """Copes files from DUT to server.""" 859 logging.info('Copying files from (%s) to (%s).', src, dst) 860 self.host.get_file(src, dst, delete_dest=True) 861 862 def copy_files_to_dut(self, src_dir, dst_dir): 863 """Copies files from server to DUT.""" 864 logging.info('Copying files from (%s) to (%s).', src_dir, dst_dir) 865 self.host.send_file(src_dir, dst_dir, delete_dest=True) 866 # Sync the filesystem in case we need to reboot the AP soon. 867 self.run_cmd('sync') 868 869 def run_server_cmd(self, command, timeout=60): 870 """Runs command on server; return result with output and exit code.""" 871 logging.info('Server execute: %s', command) 872 result = utils.run(command, timeout=timeout, ignore_status=True) 873 logging.info('exit_code: %d', result.exit_status) 874 logging.info('stdout:\n%s', result.stdout) 875 logging.info('stderr:\n%s', result.stderr) 876 return result 877 878 def run_cmd(self, command, timeout=300): 879 """Runs command on the DUT; return result with output and exit code.""" 880 logging.debug('DUT Execute: %s', command) 881 result = self.host.run(command, timeout=timeout, ignore_status=True) 882 logging.info('exit_code: %d', result.exit_status) 883 logging.info('stdout:\n%s', result.stdout) 884 logging.info('stderr:\n%s', result.stderr) 885 return result 886 887 def _run_ectool_cmd(self, command): 888 """Runs ectool on DUT; return result with output and exit code.""" 889 cmd = 'ectool ' + self._CROS_FP_ARG + ' ' + command 890 result = self.run_cmd(cmd) 891 return result 892 893 def _run_cros_config_cmd(self, command): 894 """Runs cros_config on DUT; return result with output and exit code.""" 895 cmd = 'cros_config ' + self._CROS_CONFIG_FINGERPRINT_PATH + ' ' \ 896 + command 897 result = self.run_cmd(cmd) 898 return result 899 900 def _run_cros_config_cmd_cat(self, command): 901 """Runs cat /run/chromeos-config/v1 on DUT; return result.""" 902 cmd = "cat /run/chromeos-config/v1/{}".format(command) 903 return self.run_cmd(cmd) 904 905 def _run_dump_fmap_cmd(self, fw_file, section): 906 """ 907 Runs "dump_fmap" on DUT for given file. 908 Returns value of given section. 909 """ 910 # Write result to stderr while redirecting stderr to stdout 911 # and dropping stdout. This is done because dump_map only writes the 912 # value read from a section to a file (will not just print it to 913 # stdout). 914 cmd = 'dump_fmap -x ' + fw_file + ' ' + section +\ 915 ':/dev/stderr /dev/stderr >& /dev/stdout > /dev/null' 916 result = self.run_cmd(cmd) 917 if result.exit_status != 0: 918 raise error.TestFail('Failed to read section: %s' % section) 919 return result.stdout.rstrip('\0') 920 921 def _run_futility_show_cmd(self, fw_file): 922 """ 923 Runs "futility show" on DUT for given file. 924 Returns stdout on success. 925 """ 926 futility_cmd = 'futility show ' + fw_file 927 result = self.run_cmd(futility_cmd) 928 if result.exit_status != 0: 929 raise error.TestFail('Unable to run futility on device') 930 return result.stdout 931 932 def _run_sha256sum_cmd(self, file_name): 933 """ 934 Runs "sha256sum" on DUT for given file. 935 Returns stdout on success. 936 """ 937 sha_cmd = 'sha256sum ' + file_name 938 result = self.run_cmd(sha_cmd) 939 if result.exit_status != 0: 940 raise error.TestFail('Unable to calculate sha256sum on device') 941 return result 942 943 def run_test(self, test_name, *args): 944 """Runs test on DUT.""" 945 logging.info('Running %s', test_name) 946 # Redirecting stderr to stdout since some commands intentionally fail 947 # and it's easier to read when everything ordered in the same output 948 test_cmd = ' '.join([os.path.join(self._dut_working_dir, test_name)] + 949 list(args) + ['2>&1']) 950 # Change the working dir so we can write files from within the test 951 # (otherwise defaults to $HOME (/root), which is not usually writable) 952 # Note that dut_working_dir is automatically cleaned up so tests don't 953 # need to worry about files from previous invocations or other tests. 954 test_cmd = '(cd ' + self._dut_working_dir + ' && ' + test_cmd + ')' 955 logging.info('Test command: %s', test_cmd) 956 result = self.run_cmd(test_cmd) 957 if result.exit_status != 0: 958 raise error.TestFail(test_name + ' failed') 959