• Home
  • Line#
  • Scopes#
  • Navigate#
  • Raw
  • Download
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 pprint
7import time
8
9from autotest_lib.client.common_lib import error
10from autotest_lib.server.cros.faft.cr50_test import Cr50Test
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    VALID_STATES = {
46        'AP' : ON_MAP,
47        'EC' : ON_MAP,
48        'AP UART' : ON_MAP,
49        'Rdd' : CONNECTED_MAP,
50        'Servo' : CONNECTED_MAP,
51        'CCD EXT' : ENABLED_MAP,
52    }
53    # RESULT_ORDER is a list of the CCD state strings. The order corresponds
54    # with the order of the key states in EXPECTED_RESULTS.
55    RESULT_ORDER = ['Rdd', 'CCD EXT', 'Servo']
56    # A dictionary containing an order of steps to verify and the expected ccd
57    # states as the value.
58    #
59    # The keys are a list of strings with the order of steps to run.
60    #
61    # The values are the expected state of [rdd, ccd ext, servo]. The ccdstate
62    # strings are in RESULT_ORDER. The order of the EXPECTED_RESULTS key states
63    # must match the order in RESULT_ORDER.
64    #
65    # There are three valid states: UNDETECTABLE, ON, or OFF. Undetectable only
66    # describes the servo state when EC uart is enabled. If the ec uart is
67    # enabled, cr50 cannot detect servo and the state becomes undetectable. All
68    # other ccdstates can only be off or on. Cr50 has a lot of different words
69    # for off off and on. So VALID_STATES can be used to convert off, on, and
70    # undetectable to the actual state strings.
71    EXPECTED_RESULTS = {
72        # The state all tests will start with. Servo and the ccd cable are
73        # disconnected.
74        'reset_ccd state' : [OFF, OFF, OFF],
75
76        # If rdd is attached all ccd functionality will be enabled, and servo
77        # will be undetectable.
78        'rdd attach' : [ON, ON, UNDETECTABLE],
79
80        # Cr50 cannot detect servo if ccd has been enabled first
81        'rdd attach, fake_servo off' : [ON, ON, UNDETECTABLE],
82        'rdd attach, fake_servo off, rdd detach' : [OFF, OFF, OFF],
83        'rdd attach, fake_servo on' : [ON, ON, UNDETECTABLE],
84        'rdd attach, fake_servo on, rdd detach' : [OFF, OFF, ON],
85        # Cr50 can detect servo after a reboot even if rdd was attached before
86        # servo.
87        'rdd attach, fake_servo on, cr50_run reboot' : [ON, ON, ON],
88        # Once servo is detached, Cr50 will immediately reenable the EC uart.
89        'rdd attach, fake_servo on, cr50_run reboot, fake_servo off' :
90            [ON, ON, UNDETECTABLE],
91
92        # Cr50 can detect a servo attach
93        'fake_servo on' : [OFF, OFF, ON],
94        # Cr50 knows servo is attached when ccd is enabled, so it wont enable
95        # uart.
96        'fake_servo on, rdd attach' : [ON, ON, ON],
97        'fake_servo on, rdd attach, cr50_run reboot' : [ON, ON, ON],
98        'fake_servo on, cr50_run reboot' : [OFF, OFF, ON],
99    }
100
101
102    def initialize(self, host, cmdline_args, full_args):
103        super(firmware_Cr50CCDServoCap, self).initialize(host, cmdline_args,
104                full_args)
105        if not hasattr(self, 'cr50'):
106            raise error.TestNAError('Test can only be run on devices with '
107                                    'access to the Cr50 console')
108
109        if self.servo.get_servo_version() != 'servo_v4_with_servo_micro':
110            raise error.TestNAError('Must use servo v4 with servo micro')
111
112        if not self.cr50.has_command('ccdstate'):
113            raise error.TestNAError('Cannot test on Cr50 with old CCD version')
114
115        if not self.cr50.servo_v4_supports_dts_mode():
116            raise error.TestNAError('Need working servo v4 DTS control')
117
118        self.check_servo_monitor()
119        # Make sure cr50 is open with testlab enabled.
120        self.fast_open(enable_testlab=True)
121        if not self.cr50.testlab_is_on():
122            raise error.TestNAError('Cr50 testlab mode needs to be enabled')
123        logging.info('Cr50 is %s', self.servo.get('cr50_ccd_level'))
124        self.cr50.set_cap('UartGscTxECRx', 'Always')
125
126
127    def cleanup(self):
128        """Reenable the EC uart"""
129        try:
130            self.fake_servo('on')
131            self.rdd('detach')
132            self.rdd('attach')
133        finally:
134            super(firmware_Cr50CCDServoCap, self).cleanup()
135
136
137    def state_matches(self, state_dict, state_name, expected_value):
138        """Check the current state. Make sure it matches expected value"""
139        valid_state = self.VALID_STATES[state_name][expected_value]
140        current_state = state_dict[state_name]
141        if isinstance(valid_state, list):
142            return current_state in valid_state
143        return current_state == valid_state
144
145
146    def check_servo_monitor(self):
147        """Make sure cr50 can detect servo connect and disconnect"""
148        # Detach ccd so EC uart won't interfere with servo detection
149        self.rdd('detach')
150        servo_detect_error = error.TestNAError("Cannot run on device that does "
151                "not support servo dectection with ec_uart_en:off/on")
152        self.fake_servo('off')
153        if not self.state_matches(self.get_ccdstate(), 'Servo', self.OFF):
154            raise servo_detect_error
155        self.fake_servo('on')
156        if not self.state_matches(self.get_ccdstate(), 'Servo', self.ON):
157            raise servo_detect_error
158
159
160    def get_ccdstate(self):
161        """Get the current Cr50 CCD states"""
162        rv = self.cr50.send_command_get_output('ccdstate',
163                                               ['ccdstate(.*)>'])[0][0]
164        # I2C isn't a reliable flag, because the hardware often doesn't support
165        # it. Remove any I2C flags from the ccdstate output.
166        rv = rv.replace(' I2C', '')
167        # Extract only the ccdstate output from rv
168        ccdstate = {}
169        for line in rv.splitlines():
170            line = line.strip()
171            if ':' in line:
172                k, v = line.split(':', 1)
173                ccdstate[k.strip()] = v.strip()
174        logging.info('Current CCD state:\n%s', pprint.pformat(ccdstate))
175        return ccdstate
176
177    def state_is_on(self, ccdstate, state_name):
178        """Returns true if the state is on"""
179        return self.state_matches(ccdstate, state_name, self.ON)
180
181
182    def check_state_flags(self, ccdstate):
183        """Check the state flags against the reset of the device state
184
185        If there is any mismatch between the device state and state flags,
186        return a list of errors.
187        """
188        flags = ccdstate['State flags']
189        ap_uart_enabled = 'UARTAP' in flags
190        ec_uart_enabled = 'UARTEC' in flags
191        output_enabled = '+TX' in flags
192        ccd_enabled = ap_uart_enabled or ec_uart_enabled or output_enabled
193        ccd_ext_is_enabled = ccdstate['CCD EXT'] == 'enabled'
194        mismatch = []
195        if ccd_enabled and not ccd_ext_is_enabled:
196            mismatch.append('CCD functionality enabled without CCD EXT')
197        if ccd_ext_is_enabled:
198            if output_enabled and self.state_is_on(ccdstate, 'Servo'):
199                mismatch.append('CCD output is enabled with servo attached')
200            if ap_uart_enabled != self.state_is_on(ccdstate, 'AP UART'):
201                mismatch.append('AP UART enabled without AP UART on')
202            if ec_uart_enabled != self.state_is_on(ccdstate, 'EC'):
203                mismatch.append('EC UART enabled without EC on')
204        return mismatch
205
206
207
208    def verify_ccdstate(self, run):
209        """Verify the current state matches the expected result from the run.
210
211        Args:
212            run: the string representing the actions that have been run.
213
214        Raises:
215            TestError if any of the states are not correct
216        """
217        if run not in self.EXPECTED_RESULTS:
218            raise error.TestError('Add results for %s to EXPECTED_RESULTS', run)
219        expected_states = self.EXPECTED_RESULTS[run]
220
221        # Wait a short time for the ccd state to settle
222        time.sleep(self.SLEEP)
223
224        ccdstate = self.get_ccdstate()
225        # Check the state flags. Make sure they're in line with the rest of
226        # ccdstate
227        mismatch = self.check_state_flags(ccdstate)
228        for i, expected_state in enumerate(expected_states):
229            name = self.RESULT_ORDER[i]
230            if expected_state == None:
231                logging.info('No expected %s state skipping check', name)
232                continue
233            # Check that the current state matches the expected state
234            if not self.state_matches(ccdstate, name, expected_state):
235                mismatch.append('%s is %r not %r' % (name, ccdstate[name],
236                                self.STATUS_MAP[expected_state]))
237        if mismatch:
238            logging.info(ccdstate)
239            raise error.TestFail('Unexpected states after %s: %s' % (run,
240                mismatch))
241
242
243    def cr50_run(self, action):
244        """Reboot cr50
245
246        @param action: string 'reboot'
247        """
248        if action == 'reboot':
249            self.cr50.reboot()
250            self.cr50.send_command('ccd testlab open')
251            time.sleep(self.SLEEP)
252
253
254    def reset_ccd(self, state=None):
255        """detach the ccd cable and disconnect servo.
256
257        State is ignored. It just exists to be consistent with the other action
258        functions.
259
260        @param state: a var that is ignored
261        """
262        self.rdd('detach')
263        self.fake_servo('off')
264
265
266    def rdd(self, state):
267        """Attach or detach the ccd cable.
268
269        @param state: string 'attach' or 'detach'
270        """
271        self.servo.set_nocheck('servo_v4_dts_mode',
272            'on' if state == 'attach' else 'off')
273        time.sleep(self.SLEEP)
274
275
276    def fake_servo(self, state):
277        """Mimic servo on/off
278
279        Cr50 monitors the servo EC uart tx signal to detect servo. If the signal
280        is pulled up, then Cr50 will think servo is connnected. Enable the ec
281        uart to enable the pullup. Disable the it to remove the pullup.
282
283        It takes some time for Cr50 to detect the servo state so wait 2 seconds
284        before returning.
285        """
286        self.servo.set('ec_uart_en', state)
287
288        # Cr50 needs time to detect the servo state
289        time.sleep(self.SLEEP)
290
291
292    def run_steps(self, steps):
293        """Do each step in steps and then verify the uart state.
294
295        The uart state is order dependent, so we need to know all of the
296        previous steps to verify the state. This will do all of the steps in
297        the string and verify the Cr50 CCD uart state after each step.
298
299        @param steps: a comma separated string with the steps to run
300        """
301        # The order of steps is separated by ', '. Remove the last step and
302        # run all of the steps before it.
303        separated_steps = steps.rsplit(', ', 1)
304        if len(separated_steps) > 1:
305            self.run_steps(separated_steps[0])
306
307        step = separated_steps[-1]
308        # The func and state are separated by ' '
309        func, state = step.split(' ')
310        logging.info('running %s', step)
311        getattr(self, func)(state)
312
313        # Verify the ccd state is correct
314        self.verify_ccdstate(steps)
315
316
317    def run_once(self):
318        """Run through TEST_CASES and verify that Cr50 enables/disables uart"""
319        for steps in self.TEST_CASES:
320            self.run_steps('reset_ccd state')
321            logging.info('TESTING: %s', steps)
322            self.run_steps(steps)
323            logging.info('VERIFIED: %s', steps)
324