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