1# Copyright 2015 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
7import shutil
8import tempfile
9
10from chromite.lib import remote_access
11from autotest_lib.client.common_lib import error
12from autotest_lib.client.common_lib import utils
13from autotest_lib.server.cros.faft.firmware_test import FirmwareTest
14
15
16class firmware_FWupdate(FirmwareTest):
17    """RO+RW firmware update using chromeos-firmware --mode=[recovery|factory]
18
19    Setup Steps:
20    1. Check the device is in normal mode for recovery or
21       Check the device is in dev mode for factory
22
23    Test Steps:
24    2. extract shellball and repack with new bios.bin and ec.bin
25    3. run --mode=recovery
26    4. reboot
27
28    Verification Steps:
29    1. Step 3 should result into a success message
30    2. Run crossystem and check fwid and ro_fwid should display the new bios
31       firmware version string.
32    4. Run ectool version to check ec version. The RO version and RW version
33       strings should display new ec firmware strings.
34    """
35
36    version = 1
37
38    SHELLBALL_ORG = '/usr/sbin/chromeos-firmwareupdate'
39    SHELLBALL_COPY = '/home/root/chromeos-firmwareupdate'
40
41    def initialize(self, host, cmdline_args):
42        dict_args = utils.args_to_dict(cmdline_args)
43        super(firmware_FWupdate, self).initialize(host, cmdline_args)
44        if not set(('new_ec', 'new_bios')).issubset(set(dict_args)):
45          raise error.TestError('Missing new_ec and/or new_bios argument')
46        self.new_ec = dict_args['new_ec']
47        self.new_bios = dict_args['new_bios']
48        if not os.path.isfile(self.new_ec) or not os.path.isfile(self.new_bios):
49          raise error.TestError('Failed to locate ec or bios file')
50        self.new_pd = ''
51        if 'new_pd' in dict_args:
52          self.new_pd = dict_args['new_pd']
53          if not os.path.isfile(self.new_pd):
54            raise error.TestError('Failed to locate pd file')
55        logging.info('EC=%s BIOS=%s PD=%s',
56                     self.new_ec, self.new_bios, self.new_pd)
57        self.mode = 'recovery'
58        if 'mode' in dict_args:
59          self.mode = dict_args['mode']
60          if self.mode == 'recovery':
61            self.switcher.setup_mode('normal')  # Set device to normal mode
62          elif self.mode == 'factory':
63            self.switcher.setup_mode('dev')   # Set device to dev mode
64          else:
65            raise error.TestError('Unknown mode:%s' % self.mode)
66
67    def local_run_cmd(self, command):
68        """Execute command on local system.
69
70        @param command: shell command to be executed on local system.
71        @returns command output.
72        """
73        logging.info('Execute %s', command)
74        output = utils.system_output(command)
75        logging.info('Output %s', output)
76        return output
77
78    def dut_run_cmd(self, command):
79        """Execute command on DUT.
80
81        @param command: shell command to be executed on DUT.
82        @returns command output.
83        """
84        logging.info('Execute %s', command)
85        output = self.faft_client.system.run_shell_command_get_output(command)
86        logging.info('Output %s', output)
87        return output
88
89    def get_pd_version(self):
90        """Get pd firmware version.
91
92        @returns pd firmware version string if available.
93        """
94        if self.new_pd:
95            return self.dut_run_cmd('mosys -k pd info')[0].split('"')[5]
96        return ''
97
98    def get_system_setup(self):
99        """Get and return DUT system params.
100
101        @returns DUT system params needed for this test.
102        """
103        return {
104          'pd_version': self.get_pd_version(),
105          'ec_version': self.faft_client.ec.get_version(),
106          'mainfw_type':
107            self.faft_client.system.get_crossystem_value('mainfw_type'),
108          'ro_fwid':
109            self.faft_client.system.get_crossystem_value('ro_fwid'),
110          'fwid':
111            self.faft_client.system.get_crossystem_value('fwid'),
112        }
113
114    def repack_shellball(self, hostname):
115        """Repack DUT shellball and replace on DUT.
116
117        @param hostname: hostname of DUT.
118        """
119        extract_dir = tempfile.mkdtemp(prefix='extract', dir='/tmp')
120
121        self.dut_run_cmd('mkdir %s' % extract_dir)
122        self.dut_run_cmd('cp %s %s' % (self.SHELLBALL_ORG, self.SHELLBALL_COPY))
123        self.dut_run_cmd('%s --sb_extract %s' % (self.SHELLBALL_COPY,
124                                                 extract_dir))
125
126        dut_access = remote_access.RemoteDevice(hostname, username='root')
127        self.dut_run_cmd('cp %s %s' % (self.SHELLBALL_ORG, self.SHELLBALL_COPY))
128
129        # Replace bin files.
130        target_file = '%s/%s' % (extract_dir, 'ec.bin')
131        dut_access.CopyToDevice(self.new_ec, target_file, mode='scp')
132        target_file = '%s/%s' % (extract_dir, 'bios.bin')
133        dut_access.CopyToDevice(self.new_bios, target_file,  mode='scp')
134
135        if self.new_pd:
136          target_file = '%s/%s' % (extract_dir, 'pd.bin')
137          dut_access.CopyToDevice(self.new_pd, target_file,  mode='scp')
138
139        self.dut_run_cmd('%s --sb_repack %s' % (self.SHELLBALL_COPY,
140                                                extract_dir))
141
142        # Call to "shar" in chromeos-firmwareupdate might fail and the repack
143        # ignore failure and exit with 0 status (http://crosbug.com/p/33719).
144        # Add additional check to ensure the repack is successful.
145        command = 'tail -1 %s' % self.SHELLBALL_COPY
146        output = self.dut_run_cmd(command)
147        if 'exit 0' not in output:
148          raise error.TestError('Failed to repack %s' % self.SHELLBALL_COPY)
149
150    def get_fw_bin_version(self):
151        """Get firmwware version from binary file.
152
153        @returns verions for bios, ec, pd
154        """
155        bios_version = self.local_run_cmd('strings %s|grep Google_|head -1'
156                                              % self.new_bios)
157        ec_version = self.local_run_cmd('strings %s|head -1' % self.new_ec)
158        pd_version = ''
159        if self.new_pd:
160            pd_version = self.local_run_cmd('strings %s|head -1' % self.new_pd)
161        return (bios_version, ec_version, pd_version)
162
163    def run_once(self, host):
164        """Run chromeos-firmwareupdate with recovery or factory mode.
165
166        @param host: host to run on
167        """
168        crossystem_before = self.get_system_setup()
169        (bios_version, ec_version, pd_version) = self.get_fw_bin_version()
170
171        # Repack shellball with new ec and bios.
172        self.repack_shellball(host.hostname)
173
174        # Flash DUT with new bios/ec.
175        command = '%s --mode=%s' % (self.SHELLBALL_COPY, self.mode)
176        self.dut_run_cmd(command)
177        host.reboot()
178
179        # Extract and verify DUT state.
180        crossystem_after = self.get_system_setup()
181        logging.info('crossystem BEFORE: %s', crossystem_before)
182        logging.info('crossystem AFTER: %s', crossystem_after)
183        logging.info('Expects bios %s', bios_version)
184        logging.info('Expects ec %s', ec_version)
185        logging.info('Expects pd %s', pd_version)
186        assert bios_version == crossystem_after['fwid']
187        assert bios_version == crossystem_after['ro_fwid']
188        assert ec_version == crossystem_after['ec_version']
189        assert pd_version == crossystem_after['pd_version']
190