1# Lint as: python2, python3 2# Copyright 2017 The Chromium OS Authors. All rights reserved. 3# Use of this source code is governed by a BSD-style license that can be 4# found in the LICENSE file. 5 6from __future__ import absolute_import 7from __future__ import division 8from __future__ import print_function 9 10import argparse 11import logging 12import re 13import six 14from six.moves import range 15 16from autotest_lib.client.common_lib import error 17 18 19RO = 'ro' 20RW = 'rw' 21BID = 'bid' 22CR50_PROD = '/opt/google/cr50/firmware/cr50.bin.prod' 23CR50_PREPVT = '/opt/google/cr50/firmware/cr50.bin.prepvt' 24CR50_STATE = '/var/cache/cr50*' 25CR50_VERSION = '/var/cache/cr50-version' 26GET_CR50_VERSION = 'cat %s' % CR50_VERSION 27GET_CR50_MESSAGES ='grep "cr50-.*\[" /var/log/messages' 28UPDATE_FAILURE = 'unexpected cr50-update exit code' 29STUB_VER = '-1.-1.-1' 30# This dictionary is used to search the gsctool output for the version strings. 31# There are two gsctool commands that will return versions: 'fwver' and 32# 'binvers'. 33# 34# 'fwver' is used to get the running RO and RW versions from cr50 35# 'binvers' gets the version strings for each RO and RW region in the given 36# file 37# 38# The value in the dictionary is the regular expression that can be used to 39# find the version strings for each region. 40# 41# --fwver 42# example output: 43# open_device 18d1:5014 44# found interface 3 endpoint 4, chunk_len 64 45# READY 46# ------- 47# start 48# target running protocol version 6 49# keyids: RO 0xaa66150f, RW 0xde88588d 50# offsets: backup RO at 0x40000, backup RW at 0x44000 51# Current versions: 52# RO 0.0.10 53# RW 0.0.21 54# match groupdict: 55# { 56# 'ro': '0.0.10', 57# 'rw': '0.0.21' 58# } 59# 60# --binvers 61# example output: 62# read 524288(0x80000) bytes from /tmp/cr50.bin 63# RO_A:0.0.10 RW_A:0.0.21[00000000:00000000:00000000] 64# RO_B:0.0.10 RW_B:0.0.21[00000000:00000000:00000000] 65# match groupdict: 66# { 67# 'rw_b': '0.0.21', 68# 'rw_a': '0.0.21', 69# 'ro_b': '0.0.10', 70# 'ro_a': '0.0.10', 71# 'bid_a': '00000000:00000000:00000000', 72# 'bid_b': '00000000:00000000:00000000' 73# } 74VERSION_RE = { 75 '--fwver' : '\nRO (?P<ro>\S+).*\nRW (?P<rw>\S+)', 76 '--binvers' : 'RO_A:(?P<ro_a>[\d\.]+).*' \ 77 'RW_A:(?P<rw_a>[\d\.]+)(\[(?P<bid_a>[\d\:A-z]+)\])?.*' \ 78 'RO_B:(?P<ro_b>\S+).*' \ 79 'RW_B:(?P<rw_b>[\d\.]+)(\[(?P<bid_b>[\d\:A-z]+)\])?.*', 80} 81UPDATE_TIMEOUT = 60 82UPDATE_OK = 1 83 84MP_BID_FLAGS = 0x7f80 85ERASED_BID_INT = 0xffffffff 86ERASED_BID_STR = hex(ERASED_BID_INT) 87# With an erased bid, the flags and board id will both be erased 88ERASED_CHIP_BID = (ERASED_BID_INT, ERASED_BID_INT, ERASED_BID_INT) 89# Any image with this board id will run on any device 90EMPTY_IMAGE_BID = '00000000:00000000:00000000' 91EMPTY_IMAGE_BID_CHARACTERS = set(EMPTY_IMAGE_BID) 92SYMBOLIC_BID_LENGTH = 4 93 94gsctool = argparse.ArgumentParser() 95gsctool.add_argument('-a', '--any', dest='universal', action='store_true') 96# use /dev/tpm0 to send the command 97gsctool.add_argument('-s', '--systemdev', dest='systemdev', action='store_true') 98gsctool.add_argument('-o', '--ccd_open', dest='ccd_open', action='store_true') 99# Any command used for something other than updating. These commands should 100# never timeout because they do not force cr50 to reboot. They should all just 101# return information about cr50 and should only have a nonzero exit status if 102# something went wrong. 103gsctool.add_argument('-b', 104 '--binvers', 105 '-f', 106 '--fwver', 107 '-g', 108 '--getbootmode', 109 '-i', 110 '--board_id', 111 '-r', 112 '--rma_auth', 113 '-F', 114 '--factory', 115 '-m', 116 '--tpm_mode', 117 '-L', 118 '--flog', 119 '-A', 120 '--get_apro_hash', 121 '-H', 122 '--erase_ap_ro_hash', 123 dest='info_cmd', 124 action='store_true') 125# upstart and post_reset will post resets instead of rebooting immediately 126gsctool.add_argument('-u', '--upstart', '-p', '--post_reset', dest='post_reset', 127 action='store_true') 128gsctool.add_argument('extras', nargs=argparse.REMAINDER) 129 130 131def AssertVersionsAreEqual(name_a, ver_a, name_b, ver_b): 132 """Raise an error ver_a isn't the same as ver_b 133 134 Args: 135 name_a: the name of section a 136 ver_a: the version string for section a 137 name_b: the name of section b 138 ver_b: the version string for section b 139 140 Raises: 141 AssertionError if ver_a is not equal to ver_b 142 """ 143 assert ver_a == ver_b, ('Versions do not match: %s %s %s %s' % 144 (name_a, ver_a, name_b, ver_b)) 145 146 147def GetNewestVersion(ver_a, ver_b): 148 """Compare the versions. Return the newest one. If they are the same return 149 None.""" 150 a = [int(x) for x in ver_a.split('.')] 151 b = [int(x) for x in ver_b.split('.')] 152 153 if a > b: 154 return ver_a 155 if b > a: 156 return ver_b 157 return None 158 159 160def GetVersion(versions, name): 161 """Return the version string from the dictionary. 162 163 Get the version for each key in the versions dictionary that contains the 164 substring name. Make sure all of the versions match and return the version 165 string. Raise an error if the versions don't match. 166 167 Args: 168 version: dictionary with the partition names as keys and the 169 partition version strings as values. 170 name: the string used to find the relevant items in versions. 171 172 Returns: 173 the version from versions or "-1.-1.-1" if an invalid RO was detected. 174 """ 175 ver = None 176 key = None 177 for k, v in six.iteritems(versions): 178 if name in k: 179 if v == STUB_VER: 180 logging.info('Detected invalid %s %s', name, v) 181 return v 182 elif ver: 183 AssertVersionsAreEqual(key, ver, k, v) 184 else: 185 ver = v 186 key = k 187 return ver 188 189 190def FindVersion(output, arg): 191 """Find the ro and rw versions. 192 193 Args: 194 output: The string to search 195 arg: string representing the gsctool option, either '--binvers' or 196 '--fwver' 197 198 Returns: 199 a tuple of the ro and rw versions 200 """ 201 versions = re.search(VERSION_RE[arg], output) 202 if not versions: 203 raise Exception('Unable to determine version from: %s' % output) 204 205 versions = versions.groupdict() 206 ro = GetVersion(versions, RO) 207 rw = GetVersion(versions, RW) 208 # --binver is the only gsctool command that may have bid keys in its 209 # versions dictionary. If no bid keys exist, bid will be None. 210 bid = GetVersion(versions, BID) 211 # Use GetBoardIdInfoString to standardize all board ids to the non 212 # symbolic form. 213 return ro, rw, GetBoardIdInfoString(bid, symbolic=False) 214 215 216def GetSavedVersion(client): 217 """Return the saved version from /var/cache/cr50-version 218 219 Some boards dont have cr50.bin.prepvt. They may still have prepvt flags. 220 It is possible that cr50-update wont successfully run in this case. 221 Return None if the file doesn't exist. 222 223 Returns: 224 the version saved in cr50-version or None if cr50-version doesn't exist 225 """ 226 if not client.path_exists(CR50_VERSION): 227 return None 228 229 result = client.run(GET_CR50_VERSION).stdout.strip() 230 return FindVersion(result, '--fwver') 231 232 233def StopTrunksd(client): 234 """Stop trunksd on the client""" 235 if 'running' in client.run('status trunksd').stdout: 236 client.run('stop trunksd') 237 238 239def GSCTool(client, args, ignore_status=False, expect_reboot=False): 240 """Run gsctool with the given args. 241 242 Args: 243 client: the object to run commands on 244 args: a list of strings that contiain the gsctool args 245 ignore_status: Ignore the exit status 246 expect_reboot: Expect a reboot 247 248 Returns: 249 the result of gsctool 250 """ 251 options = gsctool.parse_args(args) 252 253 if options.systemdev: 254 StopTrunksd(client) 255 256 # If we are updating the cr50 image, gsctool will return a non-zero exit 257 # status so we should ignore it. 258 ignore_status = not options.info_cmd or ignore_status 259 # immediate reboots are only honored if the command is sent using /dev/tpm0 260 expect_reboot = expect_reboot or ((options.systemdev or options.universal) 261 and not options.post_reset 262 and not options.info_cmd) 263 264 result = client.run('gsctool %s' % ' '.join(args), 265 ignore_status=ignore_status, 266 ignore_timeout=expect_reboot, 267 timeout=UPDATE_TIMEOUT) 268 269 # After a posted reboot, the gsctool exit code should equal 1. 270 if (result and result.exit_status and result.exit_status != UPDATE_OK and 271 not ignore_status): 272 logging.debug(result) 273 raise error.TestFail('Unexpected gsctool exit code after %s %d' % 274 (' '.join(args), result.exit_status)) 275 return result 276 277 278def GetVersionFromUpdater(client, args): 279 """Return the version from gsctool""" 280 result = GSCTool(client, args).stdout.strip() 281 return FindVersion(result, args[0]) 282 283 284def GetFwVersion(client): 285 """Get the running version using 'gsctool --fwver'""" 286 return GetVersionFromUpdater(client, ['--fwver', '-a']) 287 288 289def GetBinVersion(client, image=CR50_PROD): 290 """Get the image version using 'gsctool --binvers image'""" 291 return GetVersionFromUpdater(client, ['--binvers', image]) 292 293 294def GetVersionString(ver): 295 """Combine the RO and RW tuple into a understandable string""" 296 return 'RO %s RW %s%s' % (ver[0], ver[1], 297 ' BID %s' % ver[2] if ver[2] else '') 298 299 300def GetRunningVersion(client): 301 """Get the running Cr50 version. 302 303 The version from gsctool and /var/cache/cr50-version should be the 304 same. Get both versions and make sure they match. 305 306 Args: 307 client: the object to run commands on 308 309 Returns: 310 running_ver: a tuple with the ro and rw version strings 311 312 Raises: 313 TestFail 314 - If the version in /var/cache/cr50-version is not the same as the 315 version from 'gsctool --fwver' 316 """ 317 running_ver = GetFwVersion(client) 318 saved_ver = GetSavedVersion(client) 319 320 if saved_ver: 321 AssertVersionsAreEqual('Running', GetVersionString(running_ver), 322 'Saved', GetVersionString(saved_ver)) 323 return running_ver 324 325 326def GetActiveCr50ImagePath(client): 327 """Get the path the device uses to update cr50 328 329 Extract the active cr50 path from the cr50-update messages. This path is 330 determined by cr50-get-name based on the board id flag value. 331 332 Args: 333 client: the object to run commands on 334 335 Raises: 336 TestFail 337 - If cr50-update uses more than one path or if the path we find 338 is not a known cr50 update path. 339 """ 340 ClearUpdateStateAndReboot(client) 341 messages = client.run(GET_CR50_MESSAGES).stdout.strip() 342 paths = set(re.findall('/opt/google/cr50/firmware/cr50.bin[\S]+', messages)) 343 if not paths: 344 raise error.TestFail('Could not determine cr50-update path') 345 path = paths.pop() 346 if len(paths) > 1 or (path != CR50_PROD and path != CR50_PREPVT): 347 raise error.TestFail('cannot determine cr50 path') 348 return path 349 350 351def CheckForFailures(client, last_message): 352 """Check for any unexpected cr50-update exit codes. 353 354 This only checks the cr50 update messages that have happened since 355 last_message. If a unexpected exit code is detected it will raise an error> 356 357 Args: 358 client: the object to run commands on 359 last_message: the last cr50 message from the last update run 360 361 Returns: 362 the last cr50 message in /var/log/messages 363 364 Raises: 365 TestFail 366 - If there is a unexpected cr50-update exit code after last_message 367 in /var/log/messages 368 """ 369 messages = client.run(GET_CR50_MESSAGES).stdout.strip() 370 if last_message: 371 messages = messages.rsplit(last_message, 1)[-1].split('\n') 372 failures = [] 373 for message in messages: 374 if UPDATE_FAILURE in message: 375 failures.append(message) 376 if len(failures): 377 logging.info(messages) 378 raise error.TestFail('Detected unexpected exit code during update: ' 379 '%s' % failures) 380 return messages[-1] 381 382 383def VerifyUpdate(client, ver='', last_message=''): 384 """Verify that the saved update state is correct and there were no 385 unexpected cr50-update exit codes since the last update. 386 387 Args: 388 client: the object to run commands on 389 ver: the expected version tuple (ro ver, rw ver) 390 last_message: the last cr50 message from the last update run 391 392 Returns: 393 new_ver: a tuple containing the running ro and rw versions 394 last_message: The last cr50 update message in /var/log/messages 395 """ 396 # Check that there were no unexpected reboots from cr50-result 397 last_message = CheckForFailures(client, last_message) 398 logging.debug('last cr50 message %s', last_message) 399 400 new_ver = GetRunningVersion(client) 401 if ver != '': 402 if STUB_VER != ver[0]: 403 AssertVersionsAreEqual('Old RO', ver[0], 'Updated RO', new_ver[0]) 404 AssertVersionsAreEqual('Old RW', ver[1], 'Updated RW', new_ver[1]) 405 return new_ver, last_message 406 407 408def GetDevicePath(ext): 409 """Return the device path for the .prod or .prepvt image.""" 410 if ext == 'prod': 411 return CR50_PROD 412 elif ext == 'prepvt': 413 return CR50_PREPVT 414 raise error.TestError('Unsupported cr50 image type %r' % ext) 415 416 417def ClearUpdateStateAndReboot(client): 418 """Removes the cr50 status files in /var/cache and reboots the AP""" 419 # If any /var/cache/cr50* files exist, remove them. 420 result = client.run('ls %s' % CR50_STATE, ignore_status=True) 421 if not result.exit_status: 422 client.run('rm %s' % ' '.join(result.stdout.split())) 423 elif result.exit_status != 2: 424 # Exit status 2 means the file didn't exist. If the command fails for 425 # some other reason, raise an error. 426 logging.debug(result) 427 raise error.TestFail(result.stderr) 428 client.reboot() 429 430 431def InstallImage(client, src, dest=CR50_PROD): 432 """Copy the image at src to dest on the dut 433 434 Args: 435 client: the object to run commands on 436 src: the image location of the server 437 dest: the desired location on the dut 438 439 Returns: 440 The filename where the image was copied to on the dut, a tuple 441 containing the RO and RW version of the file 442 """ 443 # Send the file to the DUT 444 client.send_file(src, dest) 445 446 ver = GetBinVersion(client, dest) 447 client.run('sync') 448 return dest, ver 449 450 451def GetBoardIdInfoTuple(board_id_str): 452 """Convert the string into board id args. 453 454 Split the board id string board_id:(mask|board_id_inv):flags to a tuple of 455 its parts. Each element will be converted to an integer. 456 457 Returns: 458 board id int, mask|board_id_inv, and flags or None if its a universal 459 image. 460 """ 461 # In tests None is used for universal board ids. Some old images don't 462 # support getting board id, so we use None. Convert 0:0:0 to None. 463 if not board_id_str or set(board_id_str) == EMPTY_IMAGE_BID_CHARACTERS: 464 return None 465 466 board_id, param2, flags = board_id_str.split(':') 467 return GetIntBoardId(board_id), int(param2, 16), int(flags, 16) 468 469 470def GetBoardIdInfoString(board_id_info, symbolic=False): 471 """Convert the board id list or str into a symbolic or non symbolic str. 472 473 This can be used to convert the board id info list into a symbolic or non 474 symbolic board id string. It can also be used to convert a the board id 475 string into a board id string with a symbolic or non symbolic board id 476 477 Args: 478 board_id_info: A string of the form board_id:(mask|board_id_inv):flags 479 or a list with the board_id, (mask|board_id_inv), flags 480 481 Returns: 482 (board_id|symbolic_board_id):(mask|board_id_inv):flags. Will return 483 None if if the given board id info is empty or is not valid 484 """ 485 # Convert board_id_info to a tuple if it's a string. 486 if isinstance(board_id_info, six.string_types): 487 board_id_info = GetBoardIdInfoTuple(board_id_info) 488 489 if not board_id_info: 490 return None 491 492 board_id, param2, flags = board_id_info 493 # Get the hex string for board id 494 board_id = '%08x' % GetIntBoardId(board_id) 495 496 # Convert the board id hex to a symbolic board id 497 if symbolic: 498 board_id = GetSymbolicBoardId(board_id) 499 500 # Return the board_id_str:8_digit_hex_mask: 8_digit_hex_flags 501 return '%s:%08x:%08x' % (board_id, param2, flags) 502 503 504def GetSymbolicBoardId(board_id): 505 """Convert an integer board id to a symbolic string 506 507 Args: 508 board_id: the board id to convert to the symbolic board id 509 510 Returns: 511 the 4 character symbolic board id 512 """ 513 symbolic_board_id = '' 514 board_id = GetIntBoardId(board_id) 515 516 # Convert the int to a symbolic board id 517 for i in range(SYMBOLIC_BID_LENGTH): 518 symbolic_board_id += chr((board_id >> (i * 8)) & 0xff) 519 symbolic_board_id = symbolic_board_id[::-1] 520 521 # Verify the created board id is 4 characters 522 if len(symbolic_board_id) != SYMBOLIC_BID_LENGTH: 523 raise error.TestFail('Created invalid symbolic board id %s' % 524 symbolic_board_id) 525 return symbolic_board_id 526 527 528def ConvertSymbolicBoardId(symbolic_board_id): 529 """Convert the symbolic board id str to an int 530 531 Args: 532 symbolic_board_id: a ASCII string. It can be up to 4 characters 533 534 Returns: 535 the symbolic board id string converted to an int 536 """ 537 board_id = 0 538 for c in symbolic_board_id: 539 board_id = ord(c) | (board_id << 8) 540 return board_id 541 542 543def GetIntBoardId(board_id): 544 """"Return the gsctool interpretation of board_id 545 546 Args: 547 board_id: a int or string value of the board id 548 549 Returns: 550 a int representation of the board id 551 """ 552 if type(board_id) == int: 553 return board_id 554 555 if len(board_id) <= SYMBOLIC_BID_LENGTH: 556 return ConvertSymbolicBoardId(board_id) 557 558 return int(board_id, 16) 559 560 561def GetExpectedFlags(flags): 562 """If flags are not specified, gsctool will set them to 0xff00 563 564 Args: 565 flags: The int value or None 566 567 Returns: 568 the original flags or 0xff00 if flags is None 569 """ 570 return flags if flags != None else 0xff00 571 572 573def RMAOpen(client, cmd='', ignore_status=False): 574 """Run gsctool RMA commands""" 575 return GSCTool(client, ['-a', '-r', cmd], ignore_status) 576 577 578def GetChipBoardId(client): 579 """Return the board id and flags 580 581 Args: 582 client: the object to run commands on 583 584 Returns: 585 a tuple with the int values of board id, board id inv, flags 586 587 Raises: 588 TestFail if the second board id response field is not ~board_id 589 """ 590 result = GSCTool(client, ['-a', '-i']).stdout.strip() 591 board_id_info = result.split('Board ID space: ')[-1].strip().split(':') 592 board_id, board_id_inv, flags = [int(val, 16) for val in board_id_info] 593 logging.info('BOARD_ID: %x:%x:%x', board_id, board_id_inv, flags) 594 595 if board_id == board_id_inv == ERASED_BID_INT: 596 if flags == ERASED_BID_INT: 597 logging.info('board id is erased') 598 else: 599 logging.info('board id type is erased') 600 elif board_id & board_id_inv: 601 raise error.TestFail('board_id_inv should be ~board_id got %x %x' % 602 (board_id, board_id_inv)) 603 return board_id, board_id_inv, flags 604 605 606def GetChipBIDFromImageBID(image_bid, brand): 607 """Calculate a chip bid that will work with the image bid. 608 609 Returns: 610 A tuple of integers (bid type, ~bid type, bid flags) 611 """ 612 image_bid_tuple = GetBoardIdInfoTuple(image_bid) 613 # GetBoardIdInfoTuple returns None if the image isn't board id locked. 614 # Generate a Tuple of all 0s the rest of the function can use. 615 if not image_bid_tuple: 616 image_bid_tuple = (0, 0, 0) 617 618 image_bid, image_mask, image_flags = image_bid_tuple 619 if image_mask: 620 new_brand = GetSymbolicBoardId(image_bid) 621 else: 622 new_brand = brand 623 new_flags = image_flags or MP_BID_FLAGS 624 bid_type = GetIntBoardId(new_brand) 625 # If the board id type is erased, type_inv should also be unset. 626 if bid_type == ERASED_BID_INT: 627 return (ERASED_BID_INT, ERASED_BID_INT, new_flags) 628 return bid_type, 0xffffffff & ~bid_type, new_flags 629 630 631def CheckChipBoardId(client, board_id, flags, board_id_inv=None): 632 """Compare the given board_id and flags to the running board_id and flags 633 634 Interpret board_id and flags how gsctool would interpret them, then compare 635 those interpreted values to the running board_id and flags. 636 637 Args: 638 client: the object to run commands on 639 board_id: a hex str, symbolic str, or int value for board_id 640 board_id_inv: a hex str or int value of board_id_inv. Ignore 641 board_id_inv if None. board_id_inv is ~board_id unless 642 the board id is erased. In case both should be 0xffffffff. 643 flags: the int value of flags or None 644 645 Raises: 646 TestFail if the new board id info does not match 647 """ 648 # Read back the board id and flags 649 new_board_id, new_board_id_inv, new_flags = GetChipBoardId(client) 650 651 expected_board_id = GetIntBoardId(board_id) 652 expected_flags = GetExpectedFlags(flags) 653 654 if board_id_inv == None: 655 new_board_id_inv_str = '' 656 expected_board_id_inv_str = '' 657 else: 658 new_board_id_inv_str = '%08x:' % new_board_id_inv 659 expected_board_id_inv = GetIntBoardId(board_id_inv) 660 expected_board_id_inv_str = '%08x:' % expected_board_id_inv 661 662 expected_str = '%08x:%s%08x' % (expected_board_id, 663 expected_board_id_inv_str, 664 expected_flags) 665 new_str = '%08x:%s%08x' % (new_board_id, new_board_id_inv_str, new_flags) 666 667 if new_str != expected_str: 668 raise error.TestFail('Failed to set board id: expected %r got %r' % 669 (expected_str, new_str)) 670 671 672def SetChipBoardId(client, board_id, flags=None, pad=True): 673 """Sets the board id and flags 674 675 Args: 676 client: the object to run commands on 677 board_id: a string of the symbolic board id or board id hex value. If 678 the string is less than 4 characters long it will be 679 considered a symbolic value 680 flags: a int flag value. If board_id is a symbolic value, then this will 681 be ignored. 682 pad: pad any int board id, so the string is not 4 characters long. 683 684 Raises: 685 TestFail if we were unable to set the flags to the correct value 686 """ 687 if isinstance(board_id, int): 688 # gsctool will interpret any 4 character string as a RLZ code. If pad is 689 # true, pad the board id with 0s to make sure the board id isn't 4 690 # characters long. 691 board_id_arg = ('0x%08x' % board_id) if pad else hex(board_id) 692 else: 693 board_id_arg = board_id 694 if flags != None: 695 board_id_arg += ':' + hex(flags) 696 # Set the board id using the given board id and flags 697 result = GSCTool(client, ['-a', '-i', board_id_arg]).stdout.strip() 698 699 CheckChipBoardId(client, board_id, flags) 700 701def DumpFlog(client): 702 """Retrieve contents of the flash log""" 703 return GSCTool(client, ['-a', '-L']).stdout.strip() 704