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 re 9import time 10 11from autotest_lib.client.common_lib import error 12from autotest_lib.server.cros.faft.cr50_test import Cr50Test 13 14 15class firmware_Cr50ConsoleCommands(Cr50Test): 16 """ 17 Verify the cr50 console output for important commands. 18 19 This test verifies the output of pinmux, help, gpiocfg. These are the main 20 console commands we can use to check cr50 configuration. 21 """ 22 version = 1 23 24 # The board properties that are actively being used. This also excludes all 25 # ccd board properties, because they might change based on whether ccd is 26 # enabled. 27 # 28 # This information is in ec/board/cr50/scratch_reg1.h 29 RELEVANT_PROPERTIES = 0x63 30 COMPARE_LINES = '\n' 31 COMPARE_WORDS = None 32 SORTED = True 33 TESTS = [ 34 ['pinmux', 'pinmux(.*)>', COMPARE_LINES, not SORTED], 35 ['help', 'Known commands:(.*)HELP LIST.*>', COMPARE_WORDS, SORTED], 36 ['gpiocfg', 'gpiocfg(.*)>', COMPARE_LINES, not SORTED], 37 ] 38 CCD_HOOK_WAIT = 2 39 # Lists connecting the board property values to the labels. 40 # [ board property, match label, exclude label ] 41 # exclude can be none if there is no label that shoud be excluded based on 42 # the property. 43 BOARD_PROPERTIES = [ 44 [0x1, 'sps', 'i2cs'], 45 [0x2, 'i2cs', 'sps'], 46 [0x40, 'plt_rst', 'sys_rst'], 47 ] 48 49 def initialize(self, host, cmdline_args, full_args): 50 super(firmware_Cr50ConsoleCommands, self).initialize(host, cmdline_args, 51 full_args) 52 self.host = host 53 self.missing = [] 54 self.extra = [] 55 self.past_matches = {} 56 57 # Make sure the console is restricted 58 if self.cr50.get_cap('GscFullConsole')[self.cr50.CAP_REQ] == 'Always': 59 logging.info('Restricting console') 60 self.fast_open(enable_testlab=True) 61 self.cr50.set_cap('GscFullConsole', 'IfOpened') 62 time.sleep(self.CCD_HOOK_WAIT) 63 self.cr50.set_ccd_level('lock') 64 65 66 def parse_output(self, output, split_str): 67 """Split the output with the given delimeter and remove empty strings""" 68 output = output.split(split_str) if split_str else output.split() 69 cleaned_output = [] 70 for line in output: 71 # Replace whitespace characters with one space. 72 line = ' '.join(line.strip().split()) 73 if line: 74 cleaned_output.append(line) 75 return cleaned_output 76 77 78 def get_output(self, cmd, regexp, split_str, sort): 79 """Return the cr50 console output""" 80 output = self.cr50.send_safe_command_get_output(cmd, 81 [regexp])[0][1].strip() 82 logging.debug('%s output:%s\n', cmd, output) 83 84 # Record the original command output 85 results_path = os.path.join(self.resultsdir, cmd) 86 with open(results_path, 'w') as f: 87 f.write(output) 88 89 output = self.parse_output(output, split_str) 90 if sort: 91 # Sort the output ignoring any '-'s at the start of the command. 92 output.sort(key=lambda cmd: cmd.lstrip('-')) 93 if not len(output): 94 raise error.TestFail('Could not get %s output' % cmd) 95 return '\n'.join(output) + '\n' 96 97 98 def get_expected_output(self, cmd, split_str): 99 """Return the expected cr50 console output""" 100 path = os.path.join(os.path.dirname(os.path.realpath(__file__)), cmd) 101 logging.info('reading %s', path) 102 if not os.path.isfile(path): 103 raise error.TestFail('Could not find %s file %s' % (cmd, path)) 104 105 with open(path, 'r') as f: 106 contents = f.read() 107 108 return self.parse_output(contents, split_str) 109 110 111 def check_command(self, cmd, regexp, split_str, sort): 112 """Compare the actual console command output to the expected output""" 113 expected_output = self.get_expected_output(cmd, split_str) 114 output = self.get_output(cmd, regexp, split_str, sort) 115 diff_info = difflib.unified_diff(expected_output, output.splitlines()) 116 logging.debug('%s DIFF:\n%s', cmd, '\n'.join(diff_info)) 117 missing = [] 118 extra = [] 119 for regexp in expected_output: 120 match = re.search(regexp, output) 121 if match: 122 # Update the past_matches dict with the matches from this line. 123 # 124 # Make sure if matches for any keys existed before, they exist 125 # now and if they didn't exist, they don't exist now. 126 for k, v in match.groupdict().iteritems(): 127 old_val = self.past_matches.get(k, [v, v])[0] 128 if old_val and not v: 129 missing.append('%s:%s' % (k, regexp)) 130 elif not old_val and v: 131 extra.append('%s:%s' % (k, v)) 132 else: 133 self.past_matches[k] = [v, regexp] 134 135 # Remove the matching string from the output. 136 output, n = re.subn('%s\s*' % regexp, '', output, 1) 137 if not n: 138 missing.append(regexp) 139 140 141 if missing: 142 self.missing.append('%s-(%s)' % (cmd, ', '.join(missing))) 143 output = output.strip() 144 if output: 145 extra.extend(output.split('\n')) 146 if extra: 147 self.extra.append('%s-(%s)' % (cmd, ', '.join(extra))) 148 149 150 def get_image_properties(self): 151 """Save the board properties 152 153 The saved board property flags will not include oboslete flags or the wp 154 setting. These won't change the gpio or pinmux settings. 155 """ 156 brdprop = self.cr50.get_board_properties() 157 self.include = [] 158 self.exclude = [] 159 for prop, include, exclude in self.BOARD_PROPERTIES: 160 if prop & brdprop: 161 self.include.append(include) 162 if exclude: 163 self.exclude.append(exclude) 164 else: 165 self.exclude.append(include) 166 # use the major version to determine prePVT or MP. prePVT have even 167 # major versions. prod have odd 168 version = self.cr50.get_version().split('.')[1] 169 if 'mp' in self.servo.get('cr50_version'): 170 self.include.append('mp') 171 self.exclude.append('prepvt') 172 else: 173 self.exclude.append('mp') 174 self.include.append('prepvt') 175 logging.info('%s brdprop 0x%x: %s', self.servo.get('ec_board'), 176 brdprop, ', '.join(self.include)) 177 178 179 def run_once(self, host): 180 """Verify the Cr50 gpiocfg, pinmux, and help output.""" 181 err = [] 182 test_err = [] 183 self.get_image_properties() 184 for command, regexp, split_str, sort in self.TESTS: 185 self.check_command(command, regexp, split_str, sort) 186 187 if (not self.past_matches.get('ccd_has_been_enabled', 0) and 188 self.past_matches.get('ccd_enabled', 0)): 189 err.append('Inconsistent ccd settings') 190 191 if len(self.missing): 192 err.append('MISSING OUTPUT: ' + ', '.join(self.missing)) 193 if len(self.extra): 194 err.append('EXTRA OUTPUT: ' + ', '.join(self.extra)) 195 logging.info(self.past_matches) 196 197 if len(err): 198 raise error.TestFail('\t'.join(err)) 199 200 # Check all of the labels we did/didn't match. Make sure they match the 201 # expected cr50 settings. Raise a test error if there are any mismatches 202 missing_labels = [] 203 for label in self.include: 204 if label in self.past_matches and not self.past_matches[label][0]: 205 missing_labels.append(label) 206 extra_labels = [] 207 for label in self.exclude: 208 if label in self.past_matches and self.past_matches[label][0]: 209 extra_labels.append(label) 210 if missing_labels: 211 test_err.append('missing: %s' % ', '.join(missing_labels)) 212 if extra_labels: 213 test_err.append('matched: %s' % ', '.join(extra_labels)) 214 if test_err: 215 raise error.TestError('\t'.join(test_err)) 216