• Home
  • Line#
  • Scopes#
  • Navigate#
  • Raw
  • Download
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 logging
6import os
7
8from autotest_lib.client.common_lib import error
9from autotest_lib.client.common_lib.cros import cr50_utils
10from autotest_lib.server.cros.faft.cr50_test import Cr50Test
11
12
13class firmware_Cr50Update(Cr50Test):
14    """
15    Verify a dut can update to the given image.
16
17    Copy the new image onto the device and clear the update state to force
18    cr50-update to run. The test will fail if Cr50 does not update or if the
19    update script encounters any errors.
20
21    @param image: the location of the update image
22    @param image_type: string representing the image type. If it is "dev" then
23                       don't check the RO versions when comparing versions.
24    """
25    version = 1
26    UPDATE_TIMEOUT = 20
27    ERASE_NVMEM = "erase_nvmem"
28
29    DEV_NAME = "dev_image"
30    OLD_RELEASE_NAME = "old_release_image"
31    RELEASE_NAME = "release_image"
32    SUCCESS = 0
33    UPDATE_OK = 1
34
35
36    def initialize(self, host, cmdline_args, release_path="", release_ver="",
37                   old_release_path="", old_release_ver="", dev_path="",
38                   test=""):
39        """Initialize servo and process the given images"""
40        super(firmware_Cr50Update, self).initialize(host, cmdline_args,
41                                                    restore_cr50_state=True,
42                                                    cr50_dev_path=dev_path)
43
44        if not release_ver and not os.path.isfile(release_path):
45            raise error.TestError('Need to specify a release version or path')
46
47        self.devid = self.servo.get('cr50_devid')
48
49        # Make sure ccd is disabled so it won't interfere with the update
50        self.cr50.ccd_disable()
51
52        self.rootfs_verification_disable()
53
54        self.host = host
55        self.erase_nvmem = test.lower() == self.ERASE_NVMEM
56
57        # A dict used to store relevant information for each image
58        self.images = {}
59
60        # Process the given images in order of oldest to newest. Get the version
61        # info and add them to the update order
62        self.update_order = []
63        if not self.erase_nvmem and (old_release_path or old_release_ver):
64            self.add_image_to_update_order(self.OLD_RELEASE_NAME,
65                                           old_release_path, old_release_ver)
66        self.add_image_to_update_order(self.RELEASE_NAME, release_path,
67                                       release_ver)
68        self.add_image_to_update_order(self.DEV_NAME, dev_path)
69        self.verify_update_order()
70        logging.info("Update %s", self.update_order)
71
72        self.chip_bid = None
73        self.chip_flags = None
74        chip_bid_info = cr50_utils.GetChipBoardId(self.host)
75        if chip_bid_info != cr50_utils.ERASED_CHIP_BID:
76            self.chip_bid, _, self.chip_flags = chip_bid_info
77            logging.info('chip board id will be erased during rollback. %x:%x '
78                'will be restored after rollback.',  self.chip_bid,
79                self.chip_flags)
80        else:
81            logging.info('No chip board id is set. This test will not attempt '
82                'to restore anything during rollback.')
83
84        self.device_update_path = cr50_utils.GetActiveCr50ImagePath(self.host)
85        # Update to the dev image
86        self.run_update(self.DEV_NAME)
87
88    def cleanup(self):
89        """Update Cr50 to the image it was running at the start of the test"""
90        super(firmware_Cr50Update, self).cleanup()
91
92        logging.warning('rootfs verification is disabled')
93
94        # Make sure keepalive is disabled
95        self.cr50.ccd_enable()
96        self.cr50.send_command("ccd keepalive disable")
97
98        # Running usb_update commands stops trunksd. Reboot the device to reset
99        # it
100        self.host.run('reboot')
101
102
103    def run_update(self, image_name, use_usb_update=False):
104        """Copy the image to the DUT and upate to it.
105
106        Normal updates will use the cr50-update script to update. If a rollback
107        is True, use usb_update to flash the image and then use the 'rw'
108        commands to force a rollback.
109
110        @param image_name: the key in the images dict that can be used to
111                           retrieve the image info.
112        @param use_usb_update: True if usb_updater should be used directly
113                               instead of running the update script.
114        """
115        self.cr50.ccd_disable()
116        # Get the current update information
117        image_ver, image_ver_str, image_path = self.images[image_name]
118
119        dest, ver = cr50_utils.InstallImage(self.host, image_path,
120                self.device_update_path)
121        assert ver == image_ver, "Install failed"
122        image_rw = image_ver[1]
123
124        running_ver = cr50_utils.GetRunningVersion(self.host)
125        running_ver_str = cr50_utils.GetVersionString(running_ver)
126
127        # If the given image is older than the running one, then we will need
128        # to do a rollback to complete the update.
129        rollback = (cr50_utils.GetNewestVersion(running_ver[1], image_rw) !=
130                    image_rw)
131        logging.info("Attempting %s from %s to %s",
132                     "rollback" if rollback else "update", running_ver_str,
133                     image_ver_str)
134
135        # If a rollback is needed, flash the image into the inactive partition,
136        # on or use usb_update to update to the new image if it is requested.
137        if use_usb_update or rollback:
138            self.cr50_update(image_path, rollback=rollback,
139                chip_bid=self.chip_bid, chip_flags=self.chip_flags,
140                erase_nvmem=self.erase_nvmem)
141            self.check_state((self.checkers.crossystem_checker,
142                              {'mainfw_type': 'normal'}))
143
144        # Cr50 is going to reject an update if it hasn't been up for more than
145        # 60 seconds. Wait until that passes before trying to run the update.
146        self.cr50.wait_until_update_is_allowed()
147
148        # Running the usb update or rollback will enable ccd. Disable it again.
149        self.cr50.ccd_disable()
150
151        # Get the last cr50 update related message from /var/log/messages
152        last_message = cr50_utils.CheckForFailures(self.host, '')
153
154        # Clear the update state and reboot, so cr50-update will run again.
155        cr50_utils.ClearUpdateStateAndReboot(self.host)
156
157        # The cr50 updates happen over /dev/tpm0. It takes a while. After
158        # cr50-update has finished, cr50 should reboot. Wait until this happens
159        # before sending anymore commands.
160        self.cr50.wait_for_reboot()
161
162        # Verify the system boots normally after the update
163        self.check_state((self.checkers.crossystem_checker,
164                          {'mainfw_type': 'normal'}))
165
166        # Verify the version has been updated and that there have been no
167        # unexpected usb_updater exit codes.
168        cr50_utils.VerifyUpdate(self.host, image_ver, last_message)
169
170        logging.info("Successfully updated from %s to %s %s", running_ver_str,
171                     image_name, image_ver_str)
172
173
174    def fetch_image(self, ver=None):
175        """Fetch the image from gs and copy it to the host.
176
177        @param ver: The rw version of the prod image. If it is not None then the
178                    image will be retrieved from chromeos-localmirror otherwise
179                    it will be gotten from chromeos-localmirror-private using
180                    the devids
181        """
182        if ver:
183            bid = None
184            if '/' in ver:
185                ver, bid = ver.split('/', 1)
186            return self.download_cr50_release_image(ver, bid)
187        return self.download_cr50_debug_image(self.devid)
188
189
190    def add_image_to_update_order(self, image_name, image_path, ver=None):
191        """Process the image. Add it to the update_order list and images dict.
192
193        Copy the image to the DUT and get version information.
194
195        Store the image information in the images dictionary and add it to the
196        update_order list.
197
198        @param image_name: string that is what the image should be called. Used
199                           as the key in the images dict.
200        @param image_path: the path for the image.
201        @param ver: If the image path isn't specified, this will be used to find
202                    the cr50 image in gs://chromeos-localmirror/distfiles.
203        """
204        tmp_file = '/tmp/%s.bin' % image_name
205
206        if not os.path.isfile(image_path):
207            image_path, ver = self.fetch_image(ver)
208        else:
209            _, ver = cr50_utils.InstallImage(self.host, image_path, tmp_file)
210
211        ver_str = cr50_utils.GetVersionString(ver)
212
213        self.update_order.append(image_name)
214        self.images[image_name] = (ver, ver_str, image_path)
215        logging.info("%s stored at %s with version %s", image_name, image_path,
216                     ver_str)
217
218
219    def verify_update_order(self):
220        """Verify each image in the update order has a higher version than the
221        last.
222
223        The test uses the cr50 update script to update to the next image in the
224        update order. If the versions are not in ascending order then the update
225        won't work. Cr50 cannot update to an older version using the standard
226        update process.
227
228        Raises:
229            TestError if the versions are not in ascending order.
230        """
231        for i, name in enumerate(self.update_order[1::]):
232            rw = self.images[name][0][1]
233
234            last_name = self.update_order[i]
235            last_rw = self.images[last_name][0][1]
236            if cr50_utils.GetNewestVersion(last_rw, rw) != rw:
237                raise error.TestError("%s is version %s. %s needs to have a "
238                                      "higher version, but it has %s" %
239                                      (last_name, last_rw, name, rw))
240
241
242    def after_run_once(self):
243        """Add log printing what iteration we just completed"""
244        logging.info("Update iteration %s ran successfully", self.iteration)
245
246
247    def run_once(self):
248        """Update to each image in update_order"""
249        for name in self.update_order:
250            self.run_update(name)
251