• Home
  • Line#
  • Scopes#
  • Navigate#
  • Raw
  • Download
1# Copyright 2016 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
6
7from autotest_lib.client.common_lib import error
8from autotest_lib.server import autotest
9from autotest_lib.server import test
10from autotest_lib.server.cros.faft.rpc_proxy import RPCProxy
11
12class platform_LabFirmwareUpdate(test.test):
13    """For test or lab devices.  Test will fail if Software write protection
14       is enabled.  Test will compare the installed firmware to those in
15       the shellball.  If differ, execute chromeos-firmwareupdate
16       --mode=recovery to reset RO and RW firmware. Basic procedure are:
17
18       - check software write protect, if enable, attemp reset.
19       - fail test if software write protect is enabled.
20       - check if ec is available on DUT.
21       - get RO, RW versions of firmware, if RO != RW, update=True
22       - get shellball versions of firmware
23       - compare shellball version to DUT, update=True if shellball != DUT.
24       - run chromeos-firwmareupdate --mode=recovery if update==True
25       - reboot
26    """
27    version = 1
28
29    # TODO(kmshelton): Move most of the logic in this test to a unit tested
30    # library.
31    def initialize(self, host):
32        self.host = host
33        # Make sure the client library is on the device so that the proxy
34        # code is there when we try to call it.
35        client_at = autotest.Autotest(self.host)
36        client_at.install()
37        self.faft_client = RPCProxy(self.host)
38
39        # Check if EC, PD is available.
40        # Check if DUT software write protect is disabled, failed otherwise.
41        self._run_cmd('flashrom -p host --wp-status', checkfor='is disabled')
42        self.has_ec = False
43        mosys_output = self._run_cmd('mosys')
44        if 'EC information' in mosys_output:
45            self.has_ec = True
46            self._run_cmd('flashrom -p ec --wp-status', checkfor='is disabled')
47
48    def _run_cmd(self, command, checkfor=''):
49        """Run command on dut and return output.
50           Optionally check output contain string 'checkfor'.
51        """
52        logging.info('Execute: %s', command)
53        output = self.host.run(command, ignore_status=True).stdout
54        logging.info('Output: %s', output.split('\n'))
55        if checkfor and checkfor not in ''.join(output):
56            raise error.TestFail('Expect %s in output of %s' %
57                                 (checkfor, ' '.join(output)))
58        return output
59
60    def _get_version(self):
61        """Retrive RO, RW EC/PD version."""
62        ro = None
63        rw = None
64        lines = self._run_cmd('ectool version', checkfor='version')
65        for line in lines.splitlines():
66            if line.startswith('RO version:'):
67                parts = line.split(':')
68                ro = parts[1].strip()
69            if line.startswith('RW version:'):
70                parts = line.split(':')
71                rw = parts[1].strip()
72        return (ro, rw)
73
74    def _bios_version(self):
75        """Retrive RO, RW BIOS version."""
76        ro = self.faft_client.system.get_crossystem_value('ro_fwid')
77        rw = self.faft_client.system.get_crossystem_value('fwid')
78        return (ro, rw)
79
80    def _construct_fw_version(self, fw_ro, fw_rw):
81        """Construct a firmware version string in a consistent manner.
82
83        @param fw_ro: A string representing the version of a read-only
84                      firmware.
85        @param fw_rw: A string representing the version of a read-write
86                      firmware.
87
88        @returns a string constructed from fw_ro and fw_rw
89
90        """
91        if fw_ro == fw_rw:
92            return fw_rw
93        else:
94            return '%s,%s' % (fw_ro, fw_rw)
95
96    def _get_version_all(self):
97        """Retrive BIOS, EC, and PD firmware version.
98
99        @return firmware version tuple (bios, ec)
100        """
101        bios_version = None
102        ec_version = None
103        if self.has_ec:
104            (ec_ro, ec_rw) = self._get_version()
105            ec_version = self._construct_fw_version(ec_ro, ec_rw)
106            logging.info('Installed EC version: %s', ec_version)
107        (bios_ro, bios_rw) = self._bios_version()
108        bios_version = self._construct_fw_version(bios_ro, bios_rw)
109        logging.info('Installed BIOS version: %s', bios_version)
110        return (bios_version, ec_version)
111
112    def _get_shellball_version(self):
113        """Get shellball firmware version.
114
115        @return shellball firmware version tuple (bios, ec)
116        """
117        bios = None
118        ec = None
119        bios_ro = None
120        bios_rw = None
121        ec_ro = None
122        ec_rw = None
123        shellball = self._run_cmd('/usr/sbin/chromeos-firmwareupdate -V')
124        # TODO(kmshelton): Add a structured output option (likely a protobuf)
125        # to chromeos-firmwareupdate so the below can become less fragile.
126        for line in shellball.splitlines():
127            if line.startswith('BIOS version:'):
128                parts = line.split(':')
129                bios_ro = parts[1].strip()
130                logging.info('shellball ro bios %s', bios_ro)
131            if line.startswith('BIOS (RW) version:'):
132                parts = line.split(':')
133                bios_rw = parts[1].strip()
134                logging.info('shellball rw bios %s', bios_rw)
135            if line.startswith('EC version:'):
136                parts = line.split(':')
137                ec_ro = parts[1].strip()
138                logging.info('shellball ro ec %s', ec_ro)
139            elif line.startswith('EC (RW) version:'):
140                parts = line.split(':')
141                ec_rw = parts[1].strip()
142                logging.info('shellball rw ec %s', ec_rw)
143        # Shellballs do not always contain a RW version.
144        if bios_rw is not None:
145          bios = self._construct_fw_version(bios_ro, bios_rw)
146        else:
147          bios = bios_ro
148        if ec_rw is not None:
149          ec = self._construct_fw_version(ec_ro, ec_rw)
150        else:
151          ec = ec_ro
152        return (bios, ec)
153
154    def run_once(self, replace=True):
155        # Get DUT installed firmware versions.
156        (installed_bios, installed_ec) = self._get_version_all()
157
158        # Get shellball firmware versions.
159        (shball_bios, shball_ec) = self._get_shellball_version()
160
161        # Figure out if update is needed.
162        need_update = False
163        if installed_bios != shball_bios:
164            need_update = True
165            logging.info('BIOS mismatch %s, will update to %s',
166                         installed_bios, shball_bios)
167        if installed_ec and installed_ec != shball_ec:
168            need_update = True
169            logging.info('EC mismatch %s, will update to %s',
170                         installed_ec, shball_ec)
171
172        # Update and reboot if needed.
173        if need_update:
174            output = self._run_cmd('/usr/sbin/chromeos-firmwareupdate '
175                                   ' --mode=recovery', 'SUCCESS')
176            self.host.reboot()
177            # Check that installed firmware match the shellball.
178            (bios, ec) = self._get_version_all()
179            # TODO(kmshelton): Refactor this test to use named tuples so that
180            # the comparison is eaiser to grok.
181            if (bios != shball_bios or ec != shball_ec):
182                logging.info('shball bios/ec: %s/%s',
183                             shball_bios, shball_ec)
184                logging.info('installed bios/ec: %s/%s', bios, ec)
185                raise error.TestFail('Version mismatch after firmware update')
186            logging.info('*** Done firmware updated to match shellball. ***')
187        else:
188            logging.info('*** No firmware update is needed. ***')
189
190