• 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 difflib
6import logging
7import os
8import pprint
9import re
10import time
11
12from autotest_lib.client.common_lib import error
13from autotest_lib.server.cros.faft.cr50_test import Cr50Test
14
15
16class firmware_Cr50ConsoleCommands(Cr50Test):
17    """
18    Verify the cr50 console output for important commands.
19
20    This test verifies the output of pinmux, help, gpiocfg. These are the main
21    console commands we can use to check cr50 configuration.
22    """
23    version = 1
24
25    # The board properties that are actively being used. This also excludes all
26    # ccd board properties, because they might change based on whether ccd is
27    # enabled.
28    #
29    # This information is in ec/board/cr50/scratch_reg1.h
30    RELEVANT_PROPERTIES = 0x63
31    COMPARE_LINES = '\n'
32    COMPARE_WORDS = None
33    SORTED = True
34    CMD_RETRY_COUNT = 5
35    TESTS = [
36        ['pinmux', 'pinmux(.*)>', COMPARE_LINES, not SORTED],
37        ['help', 'Known commands:(.*)HELP LIST.*>', COMPARE_WORDS, SORTED],
38        ['gpiocfg', 'gpiocfg(.*)>', COMPARE_LINES, not SORTED],
39    ]
40    CCD_HOOK_WAIT = 2
41    # Lists connecting the board property values to the labels.
42    #   [ board property, match label, exclude label ]
43    # exclude can be none if there is no label that shoud be excluded based on
44    # the property.
45    BOARD_PROPERTIES = [
46            ['BOARD_PERIPH_CONFIG_SPI', 'sps', 'i2cs'],
47            ['BOARD_PERIPH_CONFIG_I2C', 'i2cs', 'sps,sps_ds_resume'],
48            ['BOARD_USE_PLT_RESET', 'plt_rst', 'sys_rst'],
49            [
50                    'BOARD_CLOSED_SOURCE_SET1', 'closed_source_set1',
51                    'open_source_set'
52            ],
53            ['BOARD_EC_CR50_COMM_SUPPORT', 'ec_comm', 'no_ec_comm'],
54            [
55                    'BOARD_CCD_REC_LID_PIN_DIOA1', 'rec_lid_a1',
56                    'rec_lid_a9,rec_lid_a12, i2cs,sps_ds_resume'
57            ],
58            [
59                    'BOARD_CCD_REC_LID_PIN_DIOA9', 'rec_lid_a9',
60                    'rec_lid_a1,rec_lid_a12,i2cs'
61            ],
62            [
63                    'BOARD_CCD_REC_LID_PIN_DIOA12', 'rec_lid_a12',
64                    'rec_lid_a1,rec_lid_a9,sps'
65            ],
66    ]
67    GUC_BRANCH_STR = 'cr50_v1.9308_26_0.'
68    MP_BRANCH_STR = 'cr50_v2.94_mp'
69    PREPVT_BRANCH_STR = 'cr50_v3.94_pp'
70    TOT_STR = 'cr50_v2.0.'
71    OPTIONAL_EXT = '_optional'
72
73    def initialize(self, host, cmdline_args, full_args):
74        super(firmware_Cr50ConsoleCommands, self).initialize(host, cmdline_args,
75                full_args)
76        self.host = host
77        self.missing = []
78        self.extra = []
79        self.past_matches = {}
80        self._ext = ''
81
82        # Make sure the console is restricted
83        if self.cr50.get_cap('GscFullConsole')[self.cr50.CAP_REQ] == 'Always':
84            logging.info('Restricting console')
85            self.fast_ccd_open(enable_testlab=True)
86            self.cr50.set_cap('GscFullConsole', 'IfOpened')
87            time.sleep(self.CCD_HOOK_WAIT)
88            self.cr50.set_ccd_level('lock')
89        self.is_tot_run = (full_args.get('tot_test_run', '').lower() == 'true')
90
91
92    def parse_output(self, output, split_str):
93        """Split the output with the given delimeter and remove empty strings"""
94        output = output.split(split_str) if split_str else output.split()
95        cleaned_output = []
96        for line in output:
97            # Replace whitespace characters with one space.
98            line = ' '.join(line.strip().split())
99            if line:
100                cleaned_output.append(line)
101        return cleaned_output
102
103
104    def get_output(self, cmd, regexp, split_str, sort):
105        """Return the cr50 console output"""
106        old_output = []
107        output = self.cr50.send_command_retry_get_output(
108                cmd, [regexp], safe=True, compare_output=True,
109                retries=10)[0][1].strip()
110
111        # Record the original command output
112        results_path = os.path.join(self.resultsdir, cmd)
113        with open(results_path, 'w') as f:
114            f.write(output)
115
116        output = self.parse_output(output, split_str)
117        if sort:
118            # Sort the output ignoring any '-'s at the start of the command.
119            output.sort(key=lambda cmd: cmd.lstrip('-'))
120        if not len(output):
121            raise error.TestFail('Could not get %s output' % cmd)
122        return '\n'.join(output) + '\n'
123
124
125    def get_expected_output(self, cmd, split_str):
126        """Return the expected cr50 console output"""
127        file_dir = os.path.dirname(os.path.realpath(__file__))
128        path = os.path.join(file_dir, cmd + self._ext)
129        if not os.path.isfile(path):
130            path = os.path.join(file_dir, cmd)
131        if not os.path.isfile(path):
132            raise error.TestFail('Could not find %s file %s' % (cmd, path))
133        logging.info('reading %s', path)
134
135        with open(path, 'r') as f:
136            contents = f.read()
137
138        return self.parse_output(contents, split_str)
139
140
141    def check_command(self, cmd, regexp, split_str, sort):
142        """Compare the actual console command output to the expected output"""
143        expected_output = self.get_expected_output(cmd, split_str)
144        output = self.get_output(cmd, regexp, split_str, sort)
145        diff_info = difflib.unified_diff(expected_output, output.splitlines())
146        logging.debug('%s DIFF:\n%s', cmd, '\n'.join(diff_info))
147        missing = []
148        extra = []
149        for regexp in expected_output:
150            match = re.search(regexp, output)
151            if match:
152                # Update the past_matches dict with the matches from this line.
153                #
154                # Make sure if matches for any keys existed before, they exist
155                # now and if they didn't exist, they don't exist now.
156                for k, v in match.groupdict().items():
157                    old_val = self.past_matches.get(k, [v, v])[0]
158
159                    # If there's an optional key, then the value may or may not
160                    # match. Save any value that's not none, so the test can
161                    # verify boards correctly exclude optional lines.
162                    if self.OPTIONAL_EXT in k:
163                        self.past_matches[k] = [old_val or v, regexp]
164                    elif old_val and not v:
165                        missing.append('%s:%s' % (k, regexp))
166                    elif not old_val and v:
167                        extra.append('%s:%s' % (k, v))
168                    else:
169                        self.past_matches[k] = [v, regexp]
170
171            # Remove the matching string from the output.
172            output, n = re.subn('%s\s*' % regexp, '', output, 1)
173            if not n:
174                missing.append(regexp)
175
176
177        if missing:
178            self.missing.append('%s-(%s)' % (cmd, ', '.join(missing)))
179        output = output.strip()
180        if output:
181            extra.extend(output.split('\n'))
182        if extra:
183            self.extra.append('%s-(%s)' % (cmd, ', '.join(extra)))
184
185
186    def get_image_properties(self):
187        """Save the board properties
188
189        The saved board property flags will not include oboslete flags or the wp
190        setting. These won't change the gpio or pinmux settings.
191        """
192        self.include = []
193        self.exclude = []
194        for prop, include, exclude in self.BOARD_PROPERTIES:
195            if self.cr50.uses_board_property(prop):
196                self.include.extend(include.split(','))
197                if exclude:
198                    self.exclude.extend(exclude.split(','))
199            else:
200                self.exclude.append(include)
201        version = self.cr50.get_full_version()
202        # Factory images end with 22. Expect guc attributes if the version
203        # ends in 22.
204        if self.GUC_BRANCH_STR in version:
205            self._ext = '.guc'
206            self.include.append('guc')
207            self.exclude.append('tot')
208            self.exclude.append('prepvt')
209        elif self.is_tot_run or self.TOT_STR in version:
210            # TOT isn't that controlled. It may include prepvt, mp, or guc
211            # changes. Don't exclude any branches.
212            self.include.append('tot')
213        elif self.MP_BRANCH_STR in version:
214            self.include.append('mp')
215
216            self.exclude.append('prepvt')
217            self.exclude.append('guc')
218            self.exclude.append('tot')
219        elif self.PREPVT_BRANCH_STR in version:
220            self.include.append('prepvt')
221
222            self.exclude.append('mp')
223            self.exclude.append('guc')
224            self.exclude.append('tot')
225        else:
226            raise error.TestNAError('Unsupported branch %s', version)
227        brdprop = self.cr50.get_board_properties()
228        logging.info('brdprop: 0x%x', brdprop)
229        logging.info('include: %s', ', '.join(self.include))
230        logging.info('exclude: %s', ', '.join(self.exclude))
231
232
233    def run_once(self, host):
234        """Verify the Cr50 gpiocfg, pinmux, and help output."""
235        err = []
236        test_err = []
237        self.get_image_properties()
238        for command, regexp, split_str, sort in self.TESTS:
239            self.check_command(command, regexp, split_str, sort)
240
241        if len(self.missing):
242            err.append('MISSING OUTPUT: ' + ', '.join(self.missing))
243        if len(self.extra):
244            err.append('EXTRA OUTPUT: ' + ', '.join(self.extra))
245        logging.debug("Past matches:\n%s", pprint.pformat(self.past_matches))
246
247        if len(err):
248            raise error.TestFail('\t'.join(err))
249
250        # Check all of the labels we did/didn't match. Make sure they match the
251        # expected cr50 settings. Raise a test error if there are any mismatches
252        missing_labels = []
253        for label in self.include:
254            if label in self.past_matches and not self.past_matches[label][0]:
255                missing_labels.append(label)
256        extra_labels = []
257        for label in self.exclude:
258            if label in self.past_matches and self.past_matches[label][0]:
259                extra_labels.append(label)
260            label = label + self.OPTIONAL_EXT
261            if label in self.past_matches and self.past_matches[label][0]:
262                extra_labels.append(label)
263        if missing_labels:
264            test_err.append('missing: %s' % ', '.join(missing_labels))
265        if extra_labels:
266            test_err.append('matched: %s' % ', '.join(extra_labels))
267        if test_err:
268            raise error.TestError('\t'.join(test_err))
269