Home | History | Annotate | Download | only in firmware_Cr50CCDServoCap
      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 
      5 import logging
      6 import pprint
      7 import time
      8 
      9 from autotest_lib.client.common_lib import error
     10 from autotest_lib.server.cros.faft.cr50_test import Cr50Test
     11 
     12 
     13 class 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