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, pd] 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    def initialize(self, host):
30        self.host = host
31        # Make sure the client library is on the device so that the proxy
32        # code is there when we try to call it.
33        client_at = autotest.Autotest(self.host)
34        client_at.install()
35        self.faft_client = RPCProxy(self.host)
36
37        # Check if EC, PD is available.
38        # Check if DUT software write protect is disabled, failed otherwise.
39        self._run_cmd('flashrom -p host --wp-status', checkfor='is disabled')
40        self.has_ec = False
41        self.has_pd = False
42        mosys_output = self._run_cmd('mosys')
43        if 'EC information' in mosys_output:
44            self.has_ec = True
45            self._run_cmd('flashrom -p ec --wp-status', checkfor='is disabled')
46        if 'PD information' in mosys_output:
47            self.has_pd = True
48            self._run_cmd('flashrom -p ec:dev=1 --wp-status',
49                         checkfor='is disabled')
50
51    def _run_cmd(self, command, checkfor=''):
52        """Run command on dut and return output.
53           Optionally check output contain string 'checkfor'.
54        """
55        logging.info('Execute: %s', command)
56        output = self.host.run(command, ignore_status=True).stdout
57        logging.info('Output: %s', output.split('\n'))
58        if checkfor and checkfor not in ''.join(output):
59            raise error.TestFail('Expect %s in output of %s' %
60                                 (checkfor, ' '.join(output)))
61        return output
62
63    def _get_version(self, pd=False):
64        """Retrive RO, RW EC/PD version."""
65        ro = None
66        rw = None
67        opt_arg = ''
68        if pd: opt_arg = '--dev=1'
69        lines = self._run_cmd('/usr/sbin/ectool %s version' % opt_arg)
70        for line in lines.splitlines():
71            if line.startswith('RO version:'):
72                parts = line.split()
73                ro = parts[2].strip()
74            if line.startswith('RW version:'):
75                parts = line.split()
76                rw = parts[2].strip()
77        return (ro, rw)
78
79    def _bios_version(self):
80        """Retrive RO, RW BIOS version."""
81        ro = self.faft_client.system.get_crossystem_value('ro_fwid')
82        rw = self.faft_client.system.get_crossystem_value('fwid')
83        return (ro, rw)
84
85    def _get_version_all(self):
86        """Retrive BIOS, EC, and PD firmware version.
87
88        @return firmware version tuple (bios, ec, pd)
89        """
90        pd_version = None
91        ec_version = None
92        bios_version = None
93        if self.has_ec:
94            (ec_ro, ec_rw) = self._get_version()
95            if ec_ro == ec_rw:
96                ec_version = ec_rw
97            else:
98                ec_version = '%s,%s' % (ec_ro, ec_rw)
99            logging.info('Installed EC version: %s', ec_version)
100        if self.has_pd:
101            (pd_ro, pd_rw) = self._get_version(pd=True)
102            if pd_ro == pd_rw:
103                pd_version = pd_rw
104            else:
105                pd_version = '%s,%s' % (pd_ro, pd_rw)
106            logging.info('Installed PD version: %s', pd_version)
107        (bios_ro, bios_rw) = self._bios_version()
108        if bios_ro == bios_rw:
109            bios_version = bios_rw
110        else:
111            bios_version = '%s,%s' % (bios_ro, bios_rw)
112        logging.info('Installed BIOS version: %s', bios_version)
113        return (bios_version, ec_version, pd_version)
114
115    def _get_shellball_version(self):
116        """Get shellball firmware version.
117
118        @return shellball firmware version tuple (bios, ec, pd)
119        """
120        ec = None
121        bios = None
122        pd = None
123        shellball = self._run_cmd('/usr/sbin/chromeos-firmwareupdate -V')
124        for line in shellball.splitlines():
125            if line.startswith('BIOS version:'):
126                parts = line.split()
127                bios = parts[2].strip()
128                logging.info('shellball bios %s', bios)
129            elif line.startswith('EC version:'):
130                parts = line.split()
131                ec = parts[2].strip()
132                logging.info('shellball ec %s', ec)
133            elif line.startswith('PD version:'):
134                parts = line.split()
135                pd = parts[2].strip()
136                logging.info('shellball pd %s', pd)
137        return (bios, ec, pd)
138
139    def run_once(self, replace=True):
140        # Get DUT installed firmware versions.
141        (installed_bios, installed_ec, installed_pd) = self._get_version_all()
142
143        # Get shellball firmware versions.
144        (shball_bios, shball_ec, shball_pd) = self._get_shellball_version()
145
146        # Figure out if update is needed.
147        need_update = False
148        if installed_bios != shball_bios:
149            need_update = True
150            logging.info('BIOS mismatch %s, will update to %s',
151                         installed_bios, shball_bios)
152        if installed_ec and installed_ec != shball_ec:
153            need_update = True
154            logging.info('EC mismatch %s, will update to %s',
155                         installed_ec, shball_ec)
156        if installed_pd != shball_pd:
157            need_update = True
158            logging.info('PD mismatch %s, will update to %s',
159                         installed_pd, shball_pd)
160
161        # Update and reboot if needed.
162        if need_update:
163            output = self._run_cmd('/usr/sbin/chromeos-firmwareupdate '
164                                   ' --mode=recovery', '(recovery) completed.')
165            self.host.reboot()
166            # Check that installed firmware match the shellball.
167            (bios, ec, pd) = self._get_version_all()
168            if (bios != shball_bios or ec != shball_ec or pd != shball_pd):
169                logging.info('shball bios/ec/pd: %s/%s/%s',
170                             shball_bios, shball_ec, shball_pd)
171                logging.info('installed bios/ec/pd: %s/%s/%s', bios, ec, pd)
172                raise error.TestFail('Version mismatch after firmware update')
173            logging.info('*** Done firmware updated to match shellball. ***')
174        else:
175            logging.info('*** No firmware update is needed. ***')
176
177