xref: /aosp_15_r20/external/autotest/server/site_tests/firmware_Cr50Update/firmware_Cr50Update.py (revision 9c5db1993ded3edbeafc8092d69fe5de2ee02df7)
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    POST_INSTALL = "post_install"
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="", test="",
38                   full_args={}):
39        """Initialize servo and process the given images"""
40        super(firmware_Cr50Update, self).initialize(host, cmdline_args,
41                                                    full_args,
42                                                    restore_cr50_image=True)
43        self.test_post_install = test.lower() == self.POST_INSTALL
44
45        if not release_ver and not os.path.isfile(release_path):
46            release_path = self.get_saved_cr50_original_path()
47            logging.info('Using device image as release')
48
49        # Make sure ccd is disabled so it won't interfere with the update
50        self.cr50.ccd_disable()
51
52        self.make_rootfs_writable()
53
54        self.host = host
55
56        # A dict used to store relevant information for each image
57        self.images = {}
58
59        # Process the given images in order of oldest to newest. Get the version
60        # info and add them to the update order
61        self.update_order = []
62        if old_release_path or old_release_ver:
63            self.add_image_to_update_order(self.OLD_RELEASE_NAME,
64                                           old_release_path, old_release_ver)
65        self.add_image_to_update_order(self.RELEASE_NAME, release_path,
66                                       release_ver)
67        self.add_image_to_update_order(self.DEV_NAME,
68                                       self.get_saved_dbg_image_path())
69        self.verify_update_order()
70        logging.info("Update %s", self.update_order)
71
72        self.device_update_path = cr50_utils.GetActiveCr50ImagePath(self.host)
73        # Update to the dev image
74        self.run_update(self.DEV_NAME)
75
76
77    def run_update(self, image_name, use_usb_update=False):
78        """Copy the image to the DUT and upate to it.
79
80        Normal updates will use the cr50-update script to update. If a rollback
81        is True, use usb_update to flash the image and then use the 'rw'
82        commands to force a rollback.
83
84        @param image_name: the key in the images dict that can be used to
85                           retrieve the image info.
86        @param use_usb_update: True if usb_updater should be used directly
87                               instead of running the update script.
88        """
89        self.cr50.ccd_disable()
90        # Get the current update information
91        image_ver, image_ver_str, image_path = self.images[image_name]
92
93        dest, ver = cr50_utils.InstallImage(self.host, image_path,
94                self.device_update_path)
95        assert ver == image_ver, "Install failed"
96        image_rw = image_ver[1]
97
98        running_ver = cr50_utils.GetRunningVersion(self.host)
99        running_ver_str = cr50_utils.GetVersionString(running_ver)
100
101        # If the given image is older than the running one, then we will need
102        # to do a rollback to complete the update.
103        rollback = (cr50_utils.GetNewestVersion(running_ver[1], image_rw) !=
104                    image_rw)
105        logging.info("Attempting %s from %s to %s",
106                     "rollback" if rollback else "update", running_ver_str,
107                     image_ver_str)
108
109        # If a rollback is needed, flash the image into the inactive partition,
110        # on or use usb_update to update to the new image if it is requested.
111        if use_usb_update or rollback:
112            self.cr50_update(image_path, rollback=rollback)
113            self.check_state((self.checkers.crossystem_checker,
114                              {'mainfw_type': 'normal'}))
115
116        # Cr50 is going to reject an update if it hasn't been up for more than
117        # 60 seconds. Wait until that passes before trying to run the update.
118        self.cr50.wait_until_update_is_allowed()
119
120        # Running the usb update or rollback will enable ccd. Disable it again.
121        self.cr50.ccd_disable()
122
123        # Get the last cr50 update related message from /var/log/messages
124        last_message = cr50_utils.CheckForFailures(self.host, '')
125
126        if self.test_post_install:
127            self.post_install()
128        else:
129            self.startup_install()
130
131        # The cr50 updates happen over /dev/tpm0. It takes a while. After
132        # cr50-update has finished, cr50 should reboot. Wait until this happens
133        # before sending anymore commands.
134        self.cr50.wait_for_reboot()
135
136        # Verify the system boots normally after the update
137        self.check_state((self.checkers.crossystem_checker,
138                          {'mainfw_type': 'normal'}))
139
140        # Verify the version has been updated and that there have been no
141        # unexpected usb_updater exit codes.
142        cr50_utils.VerifyUpdate(self.host, image_ver, last_message)
143
144        logging.info("Successfully updated from %s to %s %s", running_ver_str,
145                     image_name, image_ver_str)
146
147    def post_install(self):
148        """Run the update using the post-install script"""
149        logging.info(self.host.run('/usr/share/cros/cr50-update.sh'))
150        self.host.reboot()
151
152
153    def startup_install(self):
154        """Run the update using the startup script"""
155        # Clear the update state and reboot, so cr50-update will run again.
156        cr50_utils.ClearUpdateStateAndReboot(self.host)
157
158
159    def fetch_image(self, ver=None):
160        """Fetch the image from gs and copy it to the host.
161
162        @param ver: The rw version of the prod image. If it is not None then the
163                    image will be retrieved from chromeos-localmirror otherwise
164                    it will be gotten from chromeos-localmirror-private using
165                    the devids
166        """
167        if ver:
168            bid = None
169            if '/' in ver:
170                ver, bid = ver.split('/', 1)
171            return self.download_cr50_release_image(ver, bid)
172        return self.download_cr50_debug_image()
173
174
175    def add_image_to_update_order(self, image_name, image_path, ver=None):
176        """Process the image. Add it to the update_order list and images dict.
177
178        Copy the image to the DUT and get version information.
179
180        Store the image information in the images dictionary and add it to the
181        update_order list.
182
183        @param image_name: string that is what the image should be called. Used
184                           as the key in the images dict.
185        @param image_path: the path for the image.
186        @param ver: If the image path isn't specified, this will be used to find
187                    the cr50 image in gs://chromeos-localmirror/distfiles.
188        """
189        tmp_file = '/tmp/%s.bin' % image_name
190
191        if not os.path.isfile(image_path):
192            image_path, ver = self.fetch_image(ver)
193        else:
194            _, ver = cr50_utils.InstallImage(self.host, image_path, tmp_file)
195
196        ver_str = cr50_utils.GetVersionString(ver)
197
198        self.update_order.append(image_name)
199        self.images[image_name] = (ver, ver_str, image_path)
200        logging.info("%s stored at %s with version %s", image_name, image_path,
201                     ver_str)
202
203
204    def verify_update_order(self):
205        """Verify each image in the update order has a higher version than the
206        last.
207
208        The test uses the cr50 update script to update to the next image in the
209        update order. If the versions are not in ascending order then the update
210        won't work. Cr50 cannot update to an older version using the standard
211        update process.
212
213        Raises:
214            TestError if the versions are not in ascending order.
215        """
216        for i, name in enumerate(self.update_order[1::]):
217            rw = self.images[name][0][1]
218
219            last_name = self.update_order[i]
220            last_rw = self.images[last_name][0][1]
221            if cr50_utils.GetNewestVersion(last_rw, rw) != rw:
222                raise error.TestError("%s is version %s. %s needs to have a "
223                                      "higher version, but it has %s" %
224                                      (last_name, last_rw, name, rw))
225
226
227    def after_run_once(self):
228        """Add log printing what iteration we just completed"""
229        logging.info("Update iteration %s ran successfully", self.iteration)
230
231
232    def run_once(self):
233        """Update to each image in update_order"""
234        for name in self.update_order:
235            self.run_update(name)
236