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
5import logging
6import time
7
8from autotest_lib.client.common_lib import error
9from autotest_lib.client.common_lib.cros import tpm_utils
10from autotest_lib.server import autotest
11from autotest_lib.server.cros.faft.cr50_test import Cr50Test
12
13
14class firmware_Cr50FactoryResetVC(Cr50Test):
15    """A test verifying factory mode vendor command."""
16    version = 1
17
18    FWMP_DEV_DISABLE_CCD_UNLOCK = (1 << 6)
19    # Short wait to make sure cr50 has had enough time to update the ccd state
20    SLEEP = 2
21    BOOL_VALUES = (True, False)
22
23    def initialize(self, host, cmdline_args, full_args):
24        """Initialize servo check if cr50 exists."""
25        super(firmware_Cr50FactoryResetVC, self).initialize(host, cmdline_args,
26                full_args)
27        self.host = host
28        self.fast_open(enable_testlab=True)
29        # Run factory mode disable to make sure everything is reset.
30        self.host.run('gsctool -a -F disable', ignore_status=True)
31
32        # If we can set wp to off and on, then we can control write protect
33        try:
34            self.set_wp(True)
35            self.set_wp(False)
36        except:
37            raise error.TestNAError('Cannot fully test factory mode vendor '
38                    'command without control of write protect')
39
40
41    def wp_enabled(self):
42        """Returns True if write protect is enabled."""
43        rv = self.cr50.send_command_get_output('gpioget',
44                ['(0|1)..BATT_PRES_L'])
45        logging.info(rv)
46        return not int(rv[0][1])
47
48
49    def set_wp(self, enable):
50        """Deassert BATT_PRES signal, so cr50 will think wp is off."""
51        self.cr50.send_command('ccd testlab open')
52        # TODO(mruthven): come up with servo rework, so we can control batt_pres
53        # directly.
54        #
55        # for now build a dbg image and connect BATT_PRES_L to DOIM4 and set it
56        # as an output
57        self.cr50.send_command('gpioset BATT_PRES_L %d' % (0 if enable else 1))
58        if (not self.wp_enabled()) != (not enable):
59            raise error.TestError('Could not %s write protect' %
60                    ('set' if enable else 'clear'))
61        self.cr50.set_ccd_level('lock')
62
63
64    def fwmp_ccd_lockout(self):
65        """Returns True if FWMP is locking out CCD."""
66        return 'fwmp_lock' in self.cr50.get_ccd_info()['TPM']
67
68
69    def set_fwmp_lockout(self, enable):
70        """Change the FWMP to enable or disable ccd.
71
72        Args:
73            enable: True if FWMP flags should lock out ccd.
74        """
75        logging.info('%sing FWMP ccd lockout', 'enabl' if enable else 'clear')
76        if enable:
77            flags = hex(self.FWMP_DEV_DISABLE_CCD_UNLOCK)
78            logging.info('Setting FWMP flags to %s', flags)
79            autotest.Autotest(self.host).run_test('firmware_SetFWMP',
80                    flags=flags, fwmp_cleared=True, check_client_result=True)
81
82        if (not self.fwmp_ccd_lockout()) != (not enable):
83            raise error.TestError('Could not %s fwmp lockout' %
84                    ('set' if enable else 'clear'))
85
86
87    def has_ccd_password(self):
88        """Returns True if the ccd password is set."""
89        return 'set' in self.cr50.get_ccd_info()['Password']
90
91
92    def setup_ccd_password(self, set_password):
93        """Set the Cr50 CCD password.
94
95        Args:
96            set_password: if True set the password. The password is already
97                    cleared, so if False just check the password is cleared
98        """
99        if set_password:
100            self.cr50.send_command('ccd testlab open')
101            # Set the ccd password
102            self.set_ccd_password('ccd_dummy_pw')
103        if self.has_ccd_password() != set_password:
104            raise error.TestError('Could not %s password' %
105                    ('set' if set_password else 'clear'))
106
107
108    def factory_mode_enabled(self):
109        """Returns True if factory mode is enabled."""
110        caps = self.cr50.get_cap_dict()
111        caps.pop('GscFullConsole')
112        return self.cr50.get_cap_overview(caps)[0]
113
114
115    def get_relevant_state(self):
116        """Returns cr50 factory mode check state.
117
118        If any item in state is True, that means ccd factory mode should be
119        locked out.
120        """
121        state = []
122        state.append(self.fwmp_ccd_lockout())
123        state.append(self.wp_enabled())
124        state.append(self.has_ccd_password())
125        return state
126
127    def get_state_message(self):
128        """Convert relevant state into a useful log message."""
129        fwmp, wp, password = self.get_relevant_state()
130        return 'fwmp %s wp %s password %s' % ('set' if fwmp else 'cleared',
131                'enabled' if wp else 'disabled',
132                'set' if password else 'cleared')
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        result = self.host.run('gsctool -a -F %s' % cmd,
153                ignore_status=(enable_fail or not enable))
154        logging.debug(result)
155        expect_enabled = enable and not enable_fail
156
157        time.sleep(self.SLEEP)
158        if self.factory_mode_enabled() != expect_enabled:
159            raise error.TestFail('Unexpected factory mode %s result' % cmd)
160
161
162    def run_once(self):
163        """Verify FWMP disable with different flag values."""
164        errors = []
165        # Try enabling factory mode in each valid state. Cr50 checks write
166        # protect, password, and fwmp before allowing fwmp to be enabled.
167        for lockout_ccd_with_fwmp in self.BOOL_VALUES:
168            for set_password in self.BOOL_VALUES:
169                for enable_wp in self.BOOL_VALUES:
170                    # make sure all of the ccd stuff is reset
171                    self.cr50.send_command('ccd testlab open')
172                    # Run ccd reset to make sure all ccd state is cleared
173                    self.cr50.send_command('ccd reset')
174                    # Clear the TPM owner, so we can set the ccd password and
175                    # create the FWMP
176                    tpm_utils.ClearTPMOwnerRequest(self.host,
177                            wait_for_ready=True)
178                    self.setup_ccd_password(set_password)
179                    self.set_wp(enable_wp)
180                    self.set_fwmp_lockout(lockout_ccd_with_fwmp)
181                    self.cr50.set_ccd_level('lock')
182                    logging.info('RUN: %s', self.get_state_message())
183
184                    try:
185                        self.set_factory_mode(True)
186                        self.set_factory_mode(False)
187                    except Exception, e:
188                        message = 'FAILURE %r %r' % (self.get_state_message(),
189                                e)
190                        logging.info(message)
191                        errors.append(message)
192        if errors:
193            raise error.TestFail(errors)
194