237 lines
9.4 KiB
Python
237 lines
9.4 KiB
Python
# Copyright 2017 The Chromium OS Authors. All rights reserved.
|
|
# Use of this source code is governed by a BSD-style license that can be
|
|
# found in the LICENSE file.
|
|
|
|
import logging
|
|
import os
|
|
|
|
from autotest_lib.client.common_lib import error
|
|
from autotest_lib.client.common_lib.cros import cr50_utils
|
|
from autotest_lib.server.cros import filesystem_util
|
|
from autotest_lib.server.cros.faft.cr50_test import Cr50Test
|
|
|
|
|
|
class firmware_Cr50Update(Cr50Test):
|
|
"""
|
|
Verify a dut can update to the given image.
|
|
|
|
Copy the new image onto the device and clear the update state to force
|
|
cr50-update to run. The test will fail if Cr50 does not update or if the
|
|
update script encounters any errors.
|
|
|
|
@param image: the location of the update image
|
|
@param image_type: string representing the image type. If it is "dev" then
|
|
don't check the RO versions when comparing versions.
|
|
"""
|
|
version = 1
|
|
UPDATE_TIMEOUT = 20
|
|
POST_INSTALL = "post_install"
|
|
|
|
DEV_NAME = "dev_image"
|
|
OLD_RELEASE_NAME = "old_release_image"
|
|
RELEASE_NAME = "release_image"
|
|
SUCCESS = 0
|
|
UPDATE_OK = 1
|
|
|
|
|
|
def initialize(self, host, cmdline_args, release_path="", release_ver="",
|
|
old_release_path="", old_release_ver="", test="",
|
|
full_args={}):
|
|
"""Initialize servo and process the given images"""
|
|
super(firmware_Cr50Update, self).initialize(host, cmdline_args,
|
|
full_args,
|
|
restore_cr50_image=True)
|
|
self.test_post_install = test.lower() == self.POST_INSTALL
|
|
|
|
if not release_ver and not os.path.isfile(release_path):
|
|
release_path = self.get_saved_cr50_original_path()
|
|
logging.info('Using device image as release')
|
|
|
|
# Make sure ccd is disabled so it won't interfere with the update
|
|
self.cr50.ccd_disable()
|
|
|
|
filesystem_util.make_rootfs_writable(self.host)
|
|
|
|
self.host = host
|
|
|
|
# A dict used to store relevant information for each image
|
|
self.images = {}
|
|
|
|
# Process the given images in order of oldest to newest. Get the version
|
|
# info and add them to the update order
|
|
self.update_order = []
|
|
if old_release_path or old_release_ver:
|
|
self.add_image_to_update_order(self.OLD_RELEASE_NAME,
|
|
old_release_path, old_release_ver)
|
|
self.add_image_to_update_order(self.RELEASE_NAME, release_path,
|
|
release_ver)
|
|
self.add_image_to_update_order(self.DEV_NAME,
|
|
self.get_saved_dbg_image_path())
|
|
self.verify_update_order()
|
|
logging.info("Update %s", self.update_order)
|
|
|
|
self.device_update_path = cr50_utils.GetActiveCr50ImagePath(self.host)
|
|
# Update to the dev image
|
|
self.run_update(self.DEV_NAME)
|
|
|
|
|
|
def run_update(self, image_name, use_usb_update=False):
|
|
"""Copy the image to the DUT and upate to it.
|
|
|
|
Normal updates will use the cr50-update script to update. If a rollback
|
|
is True, use usb_update to flash the image and then use the 'rw'
|
|
commands to force a rollback.
|
|
|
|
@param image_name: the key in the images dict that can be used to
|
|
retrieve the image info.
|
|
@param use_usb_update: True if usb_updater should be used directly
|
|
instead of running the update script.
|
|
"""
|
|
self.cr50.ccd_disable()
|
|
# Get the current update information
|
|
image_ver, image_ver_str, image_path = self.images[image_name]
|
|
|
|
dest, ver = cr50_utils.InstallImage(self.host, image_path,
|
|
self.device_update_path)
|
|
assert ver == image_ver, "Install failed"
|
|
image_rw = image_ver[1]
|
|
|
|
running_ver = cr50_utils.GetRunningVersion(self.host)
|
|
running_ver_str = cr50_utils.GetVersionString(running_ver)
|
|
|
|
# If the given image is older than the running one, then we will need
|
|
# to do a rollback to complete the update.
|
|
rollback = (cr50_utils.GetNewestVersion(running_ver[1], image_rw) !=
|
|
image_rw)
|
|
logging.info("Attempting %s from %s to %s",
|
|
"rollback" if rollback else "update", running_ver_str,
|
|
image_ver_str)
|
|
|
|
# If a rollback is needed, flash the image into the inactive partition,
|
|
# on or use usb_update to update to the new image if it is requested.
|
|
if use_usb_update or rollback:
|
|
self.cr50_update(image_path, rollback=rollback)
|
|
self.check_state((self.checkers.crossystem_checker,
|
|
{'mainfw_type': 'normal'}))
|
|
|
|
# Cr50 is going to reject an update if it hasn't been up for more than
|
|
# 60 seconds. Wait until that passes before trying to run the update.
|
|
self.cr50.wait_until_update_is_allowed()
|
|
|
|
# Running the usb update or rollback will enable ccd. Disable it again.
|
|
self.cr50.ccd_disable()
|
|
|
|
# Get the last cr50 update related message from /var/log/messages
|
|
last_message = cr50_utils.CheckForFailures(self.host, '')
|
|
|
|
if self.test_post_install:
|
|
self.post_install()
|
|
else:
|
|
self.startup_install()
|
|
|
|
# The cr50 updates happen over /dev/tpm0. It takes a while. After
|
|
# cr50-update has finished, cr50 should reboot. Wait until this happens
|
|
# before sending anymore commands.
|
|
self.cr50.wait_for_reboot()
|
|
|
|
# Verify the system boots normally after the update
|
|
self.check_state((self.checkers.crossystem_checker,
|
|
{'mainfw_type': 'normal'}))
|
|
|
|
# Verify the version has been updated and that there have been no
|
|
# unexpected usb_updater exit codes.
|
|
cr50_utils.VerifyUpdate(self.host, image_ver, last_message)
|
|
|
|
logging.info("Successfully updated from %s to %s %s", running_ver_str,
|
|
image_name, image_ver_str)
|
|
|
|
def post_install(self):
|
|
"""Run the update using the post-install script"""
|
|
logging.info(self.host.run('/usr/share/cros/cr50-update.sh'))
|
|
self.host.reboot()
|
|
|
|
|
|
def startup_install(self):
|
|
"""Run the update using the startup script"""
|
|
# Clear the update state and reboot, so cr50-update will run again.
|
|
cr50_utils.ClearUpdateStateAndReboot(self.host)
|
|
|
|
|
|
def fetch_image(self, ver=None):
|
|
"""Fetch the image from gs and copy it to the host.
|
|
|
|
@param ver: The rw version of the prod image. If it is not None then the
|
|
image will be retrieved from chromeos-localmirror otherwise
|
|
it will be gotten from chromeos-localmirror-private using
|
|
the devids
|
|
"""
|
|
if ver:
|
|
bid = None
|
|
if '/' in ver:
|
|
ver, bid = ver.split('/', 1)
|
|
return self.download_cr50_release_image(ver, bid)
|
|
return self.download_cr50_debug_image()
|
|
|
|
|
|
def add_image_to_update_order(self, image_name, image_path, ver=None):
|
|
"""Process the image. Add it to the update_order list and images dict.
|
|
|
|
Copy the image to the DUT and get version information.
|
|
|
|
Store the image information in the images dictionary and add it to the
|
|
update_order list.
|
|
|
|
@param image_name: string that is what the image should be called. Used
|
|
as the key in the images dict.
|
|
@param image_path: the path for the image.
|
|
@param ver: If the image path isn't specified, this will be used to find
|
|
the cr50 image in gs://chromeos-localmirror/distfiles.
|
|
"""
|
|
tmp_file = '/tmp/%s.bin' % image_name
|
|
|
|
if not os.path.isfile(image_path):
|
|
image_path, ver = self.fetch_image(ver)
|
|
else:
|
|
_, ver = cr50_utils.InstallImage(self.host, image_path, tmp_file)
|
|
|
|
ver_str = cr50_utils.GetVersionString(ver)
|
|
|
|
self.update_order.append(image_name)
|
|
self.images[image_name] = (ver, ver_str, image_path)
|
|
logging.info("%s stored at %s with version %s", image_name, image_path,
|
|
ver_str)
|
|
|
|
|
|
def verify_update_order(self):
|
|
"""Verify each image in the update order has a higher version than the
|
|
last.
|
|
|
|
The test uses the cr50 update script to update to the next image in the
|
|
update order. If the versions are not in ascending order then the update
|
|
won't work. Cr50 cannot update to an older version using the standard
|
|
update process.
|
|
|
|
Raises:
|
|
TestError if the versions are not in ascending order.
|
|
"""
|
|
for i, name in enumerate(self.update_order[1::]):
|
|
rw = self.images[name][0][1]
|
|
|
|
last_name = self.update_order[i]
|
|
last_rw = self.images[last_name][0][1]
|
|
if cr50_utils.GetNewestVersion(last_rw, rw) != rw:
|
|
raise error.TestError("%s is version %s. %s needs to have a "
|
|
"higher version, but it has %s" %
|
|
(last_name, last_rw, name, rw))
|
|
|
|
|
|
def after_run_once(self):
|
|
"""Add log printing what iteration we just completed"""
|
|
logging.info("Update iteration %s ran successfully", self.iteration)
|
|
|
|
|
|
def run_once(self):
|
|
"""Update to each image in update_order"""
|
|
for name in self.update_order:
|
|
self.run_update(name)
|