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