1# Copyright 2017 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.server.cros.faft.cr50_test import Cr50Test
10from autotest_lib.server.cros.servo import chrome_ti50
11
12
13class firmware_Cr50CCDServoCap(Cr50Test):
14    """Verify Cr50 CCD output enable/disable when servo is connected.
15
16    Verify Cr50 will enable/disable the CCD servo output capabilities when servo
17    is attached/detached.
18    """
19    version = 1
20
21    # Time used to wait for Cr50 to detect the servo state. Cr50 updates the ccd
22    # state once a second. Wait 2 seconds to be conservative.
23    SLEEP = 2
24
25    # A list of the actions we should verify
26    TEST_CASES = [
27        'fake_servo on, cr50_run reboot',
28        'fake_servo on, rdd attach, cr50_run reboot',
29
30        'rdd attach, fake_servo on, cr50_run reboot, fake_servo off',
31        'rdd attach, fake_servo on, rdd detach',
32        'rdd attach, fake_servo off, rdd detach',
33    ]
34
35    ON = 0
36    OFF = 1
37    UNDETECTABLE = 2
38    STATUS_MAP = [ 'on', 'off', 'unknown' ]
39    # Create maps for the different ccd states. Mapping each state to 'on',
40    # 'off', and 'unknown'. These lists map to the acceptable [ on values, off
41    # values, and unknown state values]
42    ON_MAP = [ 'on', 'off', '' ]
43    ENABLED_MAP = [ 'enabled', 'disabled', '' ]
44    CONNECTED_MAP = [ 'connected', 'disconnected', 'undetectable' ]
45    ASSERTED_MAP = ['asserted', 'deasserted', '']
46    VALID_STATES = {
47            'AP': ON_MAP,
48            'EC': ON_MAP,
49            'AP UART': ON_MAP,
50            'Rdd': CONNECTED_MAP,
51            'Servo': CONNECTED_MAP,
52            'CCD EXT': ENABLED_MAP,
53            'CCD_MODE': ASSERTED_MAP,
54    }
55    # TODO(mruthven): remove CCD_ENABLED_KEYS and mentions of 'CCD EXT' once
56    # prepvt and mp images use CCD_MODE.
57    # Old ccdstate uses CCD EXT. The new ccdstate output uses CCD_MODE.
58    CCD_ENABLED_KEYS = ['CCD EXT', 'CCD_MODE']
59    # RESULT_ORDER is a list of the CCD state strings. The order corresponds
60    # with the order of the key states in EXPECTED_RESULTS.
61    RESULT_ORDER = ['Rdd', 'CCD_MODE', 'Servo']
62    # A dictionary containing an order of steps to verify and the expected ccd
63    # states as the value.
64    #
65    # The keys are a list of strings with the order of steps to run.
66    #
67    # The values are the expected state of [rdd, ccd ext, servo]. The ccdstate
68    # strings are in RESULT_ORDER. The order of the EXPECTED_RESULTS key states
69    # must match the order in RESULT_ORDER.
70    #
71    # There are three valid states: UNDETECTABLE, ON, or OFF. Undetectable only
72    # describes the servo state when EC uart is enabled. If the ec uart is
73    # enabled, cr50 cannot detect servo and the state becomes undetectable. All
74    # other ccdstates can only be off or on. Cr50 has a lot of different words
75    # for off off and on. So VALID_STATES can be used to convert off, on, and
76    # undetectable to the actual state strings.
77    EXPECTED_RESULTS = {
78        # The state all tests will start with. Servo and the ccd cable are
79        # disconnected.
80        'reset_ccd state' : [OFF, OFF, OFF],
81
82        # If rdd is attached all ccd functionality will be enabled, and servo
83        # will be undetectable.
84        'rdd attach' : [ON, ON, UNDETECTABLE],
85
86        # Cr50 cannot detect servo if ccd has been enabled first
87        'rdd attach, fake_servo off' : [ON, ON, UNDETECTABLE],
88        'rdd attach, fake_servo off, rdd detach' : [OFF, OFF, OFF],
89        'rdd attach, fake_servo on' : [ON, ON, UNDETECTABLE],
90        'rdd attach, fake_servo on, rdd detach' : [OFF, OFF, ON],
91        # Cr50 can detect servo after a reboot even if rdd was attached before
92        # servo.
93        'rdd attach, fake_servo on, cr50_run reboot' : [ON, ON, ON],
94        # Once servo is detached, Cr50 will immediately reenable the EC uart.
95        'rdd attach, fake_servo on, cr50_run reboot, fake_servo off' :
96            [ON, ON, UNDETECTABLE],
97
98        # Cr50 can detect a servo attach
99        'fake_servo on' : [OFF, OFF, ON],
100        # Cr50 knows servo is attached when ccd is enabled, so it wont enable
101        # uart.
102        'fake_servo on, rdd attach' : [ON, ON, ON],
103        'fake_servo on, rdd attach, cr50_run reboot' : [ON, ON, ON],
104        'fake_servo on, cr50_run reboot' : [OFF, OFF, ON],
105    }
106
107
108    def initialize(self, host, cmdline_args, full_args):
109        super(firmware_Cr50CCDServoCap, self).initialize(host, cmdline_args,
110                full_args)
111        if not hasattr(self, 'cr50'):
112            raise error.TestNAError('Test can only be run on devices with '
113                                    'access to the Cr50 console')
114
115        if ('servo_v4' not in self.servo.get_servo_type()
116                    or not self.servo.main_device_is_flex()):
117            raise error.TestNAError('Must use servo v4 with flex(c2d2 or '
118                                    'servo_micro)')
119
120        if not self.cr50.servo_dts_mode_is_valid():
121            raise error.TestNAError('Need working servo v4 DTS control')
122
123        if not self.cr50.check_servo_monitor():
124            raise error.TestNAError('Cannot run on device that does not '
125                                    'support servo dectection with '
126                                    'ec_uart_en:off/on')
127        # Make sure cr50 is open with testlab enabled.
128        self.fast_ccd_open(enable_testlab=True)
129        if not self.cr50.testlab_is_on():
130            raise error.TestNAError('Cr50 testlab mode needs to be enabled')
131        logging.info('Cr50 is %s', self.servo.get('gsc_ccd_level'))
132        self.cr50.set_cap('UartGscTxECRx', 'Always')
133        self.ec_efs_support = (
134                self.cr50.uses_board_property('BOARD_EC_CR50_COMM_SUPPORT'))
135        self._ccd_prefix = ('' if self.servo.main_device_is_ccd() else
136                            self.servo.get_ccd_servo_device())
137        # Check EC uart if servo has ccd controls and the board has an EC.
138        self.check_ec_uart = (
139                self.servo.has_control('ec_board', prefix=self._ccd_prefix)
140                and self.check_ec_capability(suppress_warning=True))
141
142
143    def cleanup(self):
144        """Reenable the EC uart"""
145        try:
146            self.fake_servo('on')
147            self.rdd('detach')
148            self.rdd('attach')
149        finally:
150            super(firmware_Cr50CCDServoCap, self).cleanup()
151
152
153    def state_matches(self, state_dict, state_name, expected_value):
154        """Check the current state. Make sure it matches expected value"""
155        if state_name in self.CCD_ENABLED_KEYS:
156            for state_name in self.CCD_ENABLED_KEYS:
157                if state_name in state_dict:
158                    logging.info('Using %r for ccd enabled key', state_name)
159                    break
160
161        valid_state = self.VALID_STATES[state_name][expected_value]
162        # I2C isn't a reliable flag, because the hardware often doesn't support
163        # it. Remove any I2C flags from the ccdstate output.
164        current_state = state_dict[state_name].replace(' I2C', '')
165        if isinstance(valid_state, list):
166            return current_state in valid_state
167        return current_state == valid_state
168
169
170    def state_is_on(self, ccdstate, state_name):
171        """Returns true if the state is on"""
172        return self.state_matches(ccdstate, state_name, self.ON)
173
174
175    def ccd_ec_uart_works(self):
176        """Returns True if the CCD ec uart works."""
177        try:
178            self.servo.get('ec_board', prefix=self._ccd_prefix)
179            logging.info('ccd ec console is responsive')
180            return True
181        except:
182            logging.info('ccd ec console is unresponsive')
183            return False
184
185
186    def check_state_flags(self, ccdstate):
187        """Check the state flags against the reset of the device state
188
189        If there is any mismatch between the device state and state flags,
190        return a list of errors.
191        """
192        flags = ccdstate['State flags']
193        ap_uart_enabled = 'UARTAP' in flags
194        ec_uart_enabled = 'UARTEC' in flags
195        ap_uart_tx_enabled = 'UARTAP+TX' in flags
196        ec_uart_tx_enabled = 'UARTEC+TX' in flags
197        ec_usb_tx_enabled = 'USBEC+TX' in flags
198
199        ccd_ec_uart_enabled = ec_uart_tx_enabled and ec_usb_tx_enabled
200        ccd_enabled = ap_uart_enabled or ec_usb_tx_enabled
201        output_enabled = ap_uart_tx_enabled
202        if not self.ec_efs_support:
203            output_enabled |= ec_uart_tx_enabled
204            ccd_enabled |= ec_uart_enabled
205
206        ccd_mode_is_asserted = self.state_is_on(ccdstate, 'CCD_MODE')
207        mismatch = []
208        logging.info('checking state flags')
209        if ccd_enabled and not ccd_mode_is_asserted:
210            mismatch.append('CCD functionality enabled CCD_MODE asserted')
211        if ccd_mode_is_asserted:
212            if output_enabled and self.state_is_on(ccdstate, 'Servo'):
213                mismatch.append('CCD output is enabled with servo attached')
214            if not isinstance(self.cr50, chrome_ti50.ChromeTi50):
215                if ap_uart_enabled != self.state_is_on(ccdstate, 'AP UART'):
216                    mismatch.append('AP UART enabled without AP UART on')
217                if ec_uart_enabled != self.state_is_on(ccdstate, 'EC'):
218                    mismatch.append('EC UART enabled without EC on')
219            if self.check_ec_uart:
220                ccd_ec_uart_works = self.ccd_ec_uart_works()
221                if (self.servo.get('ec_uart_en') == 'off'
222                    and ccd_ec_uart_enabled and not ccd_ec_uart_works):
223                    mismatch.append('ccd ec uart does not work with EC+TX '
224                                    'enabled.')
225                if not ccd_ec_uart_enabled and ccd_ec_uart_works:
226                    mismatch.append('ccd ec uart works with EC+TX disabled.')
227        return mismatch
228
229
230
231    def verify_ccdstate(self, run):
232        """Verify the current state matches the expected result from the run.
233
234        Args:
235            run: the string representing the actions that have been run.
236
237        Raises:
238            TestError if any of the states are not correct
239        """
240        if run not in self.EXPECTED_RESULTS:
241            raise error.TestError('Add results for %s to EXPECTED_RESULTS' % run)
242        expected_states = self.EXPECTED_RESULTS[run]
243
244        # Wait a short time for the ccd state to settle
245        time.sleep(self.SLEEP)
246
247        ccdstate = self.cr50.get_ccdstate()
248        # Check the state flags. Make sure they're in line with the rest of
249        # ccdstate
250        mismatch = self.check_state_flags(ccdstate)
251        for i, expected_state in enumerate(expected_states):
252            name = self.RESULT_ORDER[i]
253            if expected_state == None:
254                logging.info('No expected %s state skipping check', name)
255                continue
256            # Check that the current state matches the expected state
257            if not self.state_matches(ccdstate, name, expected_state):
258                mismatch.append('%s is %r not %r' % (name, ccdstate[name],
259                                self.STATUS_MAP[expected_state]))
260        if mismatch:
261            logging.info(ccdstate)
262            raise error.TestFail('Unexpected states after %s: %s' % (run,
263                mismatch))
264
265
266    def cr50_run(self, action):
267        """Reboot cr50
268
269        @param action: string 'reboot'
270        """
271        if action == 'reboot':
272            self.cr50.reboot()
273            self.cr50.send_command('ccd testlab open')
274            time.sleep(self.SLEEP)
275
276
277    def reset_ccd(self, state=None):
278        """detach the ccd cable and disconnect servo.
279
280        State is ignored. It just exists to be consistent with the other action
281        functions.
282
283        @param state: a var that is ignored
284        """
285        self.rdd('detach')
286        self.fake_servo('off')
287
288
289    def rdd(self, state):
290        """Attach or detach the ccd cable.
291
292        @param state: string 'attach' or 'detach'
293        """
294        self.servo.set_dts_mode('on' if state == 'attach' else 'off')
295        time.sleep(self.SLEEP)
296
297
298    def fake_servo(self, state):
299        """Mimic servo on/off
300
301        Cr50 monitors the servo EC uart tx signal to detect servo. If the signal
302        is pulled up, then Cr50 will think servo is connnected. Enable the ec
303        uart to enable the pullup. Disable the it to remove the pullup.
304
305        It takes some time for Cr50 to detect the servo state so wait 2 seconds
306        before returning.
307        """
308        self.servo.set('ec_uart_en', state)
309
310        # Cr50 needs time to detect the servo state
311        time.sleep(self.SLEEP)
312
313
314    def run_steps(self, steps):
315        """Do each step in steps and then verify the uart state.
316
317        The uart state is order dependent, so we need to know all of the
318        previous steps to verify the state. This will do all of the steps in
319        the string and verify the Cr50 CCD uart state after each step.
320
321        @param steps: a comma separated string with the steps to run
322        """
323        # The order of steps is separated by ', '. Remove the last step and
324        # run all of the steps before it.
325        separated_steps = steps.rsplit(', ', 1)
326        if len(separated_steps) > 1:
327            self.run_steps(separated_steps[0])
328
329        step = separated_steps[-1]
330        # The func and state are separated by ' '
331        func, state = step.split(' ')
332        logging.info('running %s', step)
333        getattr(self, func)(state)
334
335        # Verify the ccd state is correct
336        self.verify_ccdstate(steps)
337
338
339    def run_once(self):
340        """Run through TEST_CASES and verify that Cr50 enables/disables uart"""
341        for steps in self.TEST_CASES:
342            self.run_steps('reset_ccd state')
343            logging.info('TESTING: %s', steps)
344            self.run_steps(steps)
345            logging.info('VERIFIED: %s', steps)
346