• Home
  • Line#
  • Scopes#
  • Navigate#
  • Raw
  • Download
1# Copyright 2018 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
5from __future__ import print_function
6
7import logging
8import time
9
10from autotest_lib.client.common_lib import error
11from autotest_lib.client.common_lib.cros import tpm_utils
12from autotest_lib.server import autotest
13from autotest_lib.server.cros.faft.cr50_test import Cr50Test
14
15
16class firmware_Cr50FactoryResetVC(Cr50Test):
17    """A test verifying factory mode vendor command."""
18    version = 1
19
20    FWMP_DEV_DISABLE_CCD_UNLOCK = (1 << 6)
21    # Short wait to make sure cr50 has had enough time to update the ccd state
22    SLEEP = 2
23    BOOL_VALUES = (True, False)
24    TPM_ERR = 'Problems reading from TPM'
25
26    def initialize(self, host, cmdline_args, full_args):
27        """Initialize servo check if cr50 exists."""
28        super(firmware_Cr50FactoryResetVC, self).initialize(host, cmdline_args,
29                full_args)
30        if not self.cr50.has_command('bpforce'):
31            raise error.TestNAError('Cannot run test without bpforce')
32        self.fast_ccd_open(enable_testlab=True)
33        # Reset ccd completely.
34        self.cr50.ccd_reset()
35
36        # If we can fake battery connect/disconnect, then we can test the vendor
37        # command.
38        try:
39            self.bp_override(True)
40            self.bp_override(False)
41        except Exception as e:
42            logging.info(e)
43            raise error.TestNAError('Cannot fully test factory mode vendor '
44                    'command without the ability to fake battery presence')
45
46    def cleanup(self):
47        """Clear the FWMP and ccd state"""
48        try:
49            self.clear_state()
50        finally:
51            super(firmware_Cr50FactoryResetVC, self).cleanup()
52
53
54    def bp_override(self, connect):
55        """Deassert BATT_PRES signal, so cr50 will think wp is off."""
56        self.cr50.send_command('ccd testlab open')
57        self.cr50.set_batt_pres_state('connect' if connect else 'disconnect',
58                                      False)
59        if self.cr50.get_batt_pres_state()[1] != connect:
60            raise error.TestError('Could not fake battery %sconnect' %
61                    ('' if connect else 'dis'))
62        self.cr50.set_ccd_level('lock')
63
64
65    def fwmp_ccd_lockout(self):
66        """Returns True if FWMP is locking out CCD."""
67        return 'fwmp_lock' in self.cr50.get_ccd_info('TPM')
68
69
70    def set_fwmp_lockout(self, enable):
71        """Change the FWMP to enable or disable ccd.
72
73        Args:
74            enable: True if FWMP flags should lock out ccd.
75        """
76        logging.info('%sing FWMP ccd lockout', 'enabl' if enable else 'clear')
77        if enable:
78            flags = hex(self.FWMP_DEV_DISABLE_CCD_UNLOCK)
79            logging.info('Setting FWMP flags to %s', flags)
80            autotest.Autotest(self.host).run_test('firmware_SetFWMP',
81                    flags=flags, fwmp_cleared=True, check_client_result=True)
82
83        if (not self.fwmp_ccd_lockout()) != (not enable):
84            raise error.TestError('Could not %s fwmp lockout' %
85                    ('set' if enable else 'clear'))
86
87
88    def setup_ccd_password(self, set_password):
89        """Set the Cr50 CCD password.
90
91        Args:
92            set_password: if True set the password. The password is already
93                    cleared, so if False just check the password is cleared
94        """
95        if set_password:
96            self.cr50.send_command('ccd testlab open')
97            # Set the ccd password
98            self.set_ccd_password(self.CCD_PASSWORD)
99        if self.cr50.password_is_reset() == set_password:
100            raise error.TestError('Could not %s password' %
101                    ('set' if set_password else 'clear'))
102
103
104    def factory_mode_enabled(self):
105        """Returns True if factory mode is enabled."""
106        caps = self.cr50.get_cap_dict()
107        caps.pop('GscFullConsole')
108        return self.cr50.get_cap_overview(caps)[0]
109
110
111    def get_relevant_state(self):
112        """Returns cr50 state that can lock out factory mode.
113
114        FWMP, battery presence, or a password can all lock out enabling factory
115        mode using the vendor command. If any item in state is True, factory
116        mode should be locked out.
117        """
118        state = []
119        state.append(self.fwmp_ccd_lockout())
120        state.append(self.cr50.get_batt_pres_state()[1])
121        state.append(not self.cr50.password_is_reset())
122        return state
123
124
125    def get_state_message(self):
126        """Convert relevant state into a useful log message."""
127        fwmp, bp, password = self.get_relevant_state()
128        return ('fwmp %s bp %sconnected password %s' %
129                ('set' if fwmp else 'cleared',
130                 '' if bp else 'dis',
131                 'set' if password else 'cleared'))
132
133
134    def factory_locked_out(self):
135        """Returns True if any state preventing factory mode is True."""
136        return True in self.get_relevant_state()
137
138
139    def set_factory_mode(self, enable):
140        """Use the vendor command to control factory mode.
141
142        Args:
143            enable: Enable factory mode if True. Disable it if False.
144        """
145        enable_fail = self.factory_locked_out() and enable
146        time.sleep(self.SLEEP)
147        logging.info('%sABLING FACTORY MODE', 'EN' if enable else 'DIS')
148        if enable:
149            logging.info('EXPECT: %s', 'failure' if enable_fail else 'success')
150        cmd = 'enable' if enable else 'disable'
151
152        self.host.run('gsctool -a -F %s' % cmd,
153                      ignore_status=(enable_fail or not enable))
154        expect_enabled = enable and not enable_fail
155
156        # Wait long enoug for cr50 to update the ccd state.
157        time.sleep(self.SLEEP)
158        if expect_enabled:
159            # Verify the tpm is disabled.
160            result = self.host.run('gsctool -af', ignore_status=True)
161            if result.exit_status != 3 or self.TPM_ERR not in result.stderr:
162                raise error.TestFail('TPM enabled after entering factory mode')
163            # Reboot the DUT to reenable TPM communications.
164            self.host.reboot()
165
166        if self.factory_mode_enabled() != expect_enabled:
167            raise error.TestFail('Unexpected factory mode %s result' % cmd)
168
169
170    def clear_state(self):
171        """Clear the FWMP and reset CCD"""
172        self.host.reboot()
173        self._try_to_bring_dut_up()
174        # Clear the FWMP
175        self.clear_fwmp()
176        # make sure all of the ccd stuff is reset
177        self.cr50.send_command('ccd testlab open')
178        # Run ccd reset to make sure all ccd state is cleared
179        self.cr50.ccd_reset()
180        # Clear the TPM owner, so we can set the ccd password and
181        # create the FWMP
182        tpm_utils.ClearTPMOwnerRequest(self.host, wait_for_ready=True)
183
184
185    def run_once(self):
186        """Verify FWMP disable with different flag values."""
187        errors = []
188        # Try enabling factory mode in each valid state. Cr50 checks write
189        # protect, password, and fwmp before allowing fwmp to be enabled.
190        for lockout_ccd_with_fwmp in self.BOOL_VALUES:
191            for set_password in self.BOOL_VALUES:
192                for connect in self.BOOL_VALUES:
193                    # Clear relevant state, so we can set the fwmp and password
194                    self.clear_state()
195
196                    # Setup the cr50 state
197                    self.setup_ccd_password(set_password)
198                    self.bp_override(connect)
199                    self.set_fwmp_lockout(lockout_ccd_with_fwmp)
200                    self.cr50.set_ccd_level('lock')
201
202                    logging.info('RUN: %s', self.get_state_message())
203
204                    try:
205                        self.set_factory_mode(True)
206                        self.set_factory_mode(False)
207                    except Exception as e:
208                        message = 'FAILURE %r %r' % (self.get_state_message(),
209                                e)
210                        logging.info(message)
211                        errors.append(message)
212        if errors:
213            raise error.TestFail(errors)
214