1# Copyright 2019 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 re 7 8from autotest_lib.client.common_lib import error 9from autotest_lib.server.cros.faft.firmware_test import FirmwareTest 10from autotest_lib.server.cros.servo import servo 11 12class firmware_ECCbiEeprom(FirmwareTest): 13 """Servo-based EC test for Cros Board Info EEPROM""" 14 version = 1 15 16 EEPROM_LOCATE_TYPE = 0 17 EEPROM_LOCATE_INDEX = 0 # Only one EEPROM ever 18 19 # Test data to attempt to write to EEPROM 20 TEST_EEPROM_DATA = ('0xaa ' * 8).strip() 21 TEST_EEPROM_DATA_2 = ('0x55 ' * 8).strip() 22 23 # Size of read and write. Use 8-bytes as this will work with EEPROMs with 24 # page size 8 or 16 bytes. We allow 8-bytes page size parts. 25 PAGE_SIZE = 8 26 NO_READ = 0 27 28 # The number of bytes we verify are both writable and write protectable 29 MAX_BYTES = 64 30 31 def initialize(self, host, cmdline_args): 32 super(firmware_ECCbiEeprom, self).initialize(host, cmdline_args) 33 # Don't bother if CBI isn't on this device. 34 if not self.check_ec_capability(['cbi']): 35 raise error.TestNAError("Nothing needs to be tested on this device") 36 self.host = host 37 cmd = 'ectool locatechip %d %d' % (self.EEPROM_LOCATE_TYPE, 38 self.EEPROM_LOCATE_INDEX) 39 cmd_out = self.faft_client.system.run_shell_command_get_output( 40 cmd, True) 41 logging.debug('Ran %s on DUT, output was: %s', cmd, cmd_out) 42 43 if len(cmd_out) > 0 and cmd_out[0].startswith('Usage'): 44 raise error.TestNAError("I2C lookup not supported yet.") 45 46 if len(cmd_out) < 1: 47 cmd_out = [''] 48 49 match = re.search('Bus: I2C; Port: (\w+); Address: (\w+)', cmd_out[0]) 50 if match is None: 51 raise error.TestFail("I2C lookup for EEPROM CBI Failed. Check " 52 "debug log for output.") 53 54 # Allow hex value parsing (i.e. base set to 0) 55 self.i2c_port = int(match.group(1), 0) 56 self.i2c_addr = int(match.group(2), 0) 57 58 # Ensure that the i2c mux is disabled on the servo as the CBI EEPROM 59 # i2c lines are shared with the servo lines on some HW designs. If the 60 # control does not exist, ignore error 61 try: 62 self.servo.set('i2c_mux_en', 'off') 63 logging.info("i2c_mux_en present and reset.") 64 except servo.ControlUnavailableError: 65 logging.info("i2c_mux_en does not exist. Ignoring.") 66 67 # Check to see if the CBI WP is decoupled. If it's decoupled, the EC 68 # will have its own signal to control the CBI WP called `EC_CBI_WP`. 69 cmd = 'ectool gpioget ec_cbi_wp' 70 cmd_status = self.faft_client.system.run_shell_command_get_status(cmd) 71 self._wp_is_decoupled = True if cmd_status == 0 else False 72 73 def _gen_write_command(self, offset, data): 74 return ('ectool i2cxfer %d %d %d %d %s' % 75 (self.i2c_port, self.i2c_addr, self.NO_READ, offset, data)) 76 77 def _read_eeprom(self, offset): 78 cmd_out = self.faft_client.system.run_shell_command_get_output( 79 'ectool i2cxfer %d %d %d %d' % 80 (self.i2c_port, self.i2c_addr, self.PAGE_SIZE, offset)) 81 if len(cmd_out) < 1: 82 raise error.TestFail( 83 "Could not read EEPROM data at offset %d" % (offset)) 84 data = re.search('Read bytes: (.+)', cmd_out[0]).group(1) 85 if data == '': 86 raise error.TestFail( 87 "Empty EEPROM read at offset %d" % (offset)) 88 return data 89 90 def _write_eeprom(self, offset, data): 91 # Note we expect this call to fail in certain scenarios, so ignore 92 # results 93 self.faft_client.system.run_shell_command_get_output( 94 self._gen_write_command(offset, data)) 95 96 def _read_write_data(self, offset): 97 before = self._read_eeprom(offset) 98 logging.info("To reset CBI that's in a bad state, run w/ WP off:\n%s", 99 self._gen_write_command(offset, before)) 100 101 if before == self.TEST_EEPROM_DATA: 102 write_data = self.TEST_EEPROM_DATA_2 103 else: 104 write_data = self.TEST_EEPROM_DATA 105 106 self._write_eeprom(offset, write_data) 107 108 after = self._read_eeprom(offset) 109 110 return before, write_data, after 111 112 def _reset_ec_and_wait_up(self): 113 self.servo.set('cold_reset', 'on') 114 self.servo.set('cold_reset', 'off') 115 self.host.wait_up(timeout=30) 116 117 def check_eeprom_write_protected(self): 118 """Checks that CBI EEPROM cannot be written to when WP is asserted""" 119 self.set_hardware_write_protect(True) 120 offset = 0 121 122 if self._wp_is_decoupled: 123 # When the CBI WP is decoupled from the main system write protect, 124 # the EC drives a latch which sets the CBI WP. This latch is only 125 # reset when EC_RST_ODL is asserted. Since the WP has changed 126 # above, toggle EC_RST_ODL in order to clear this latch. 127 logging.info( 128 "CBI WP is EC driven, resetting EC before continuing...") 129 self._reset_ec_and_wait_up() 130 131 # Additionally, EC SW WP must be set in order for the system to be 132 # locked, which is the criteria that the EC uses to assert CBI 133 # EEPROM WP or not. 134 cmd = 'flashrom -p ec --wp-enable' 135 self.faft_client.system.run_shell_command(cmd) 136 137 for offset in range(0, self.MAX_BYTES, self.PAGE_SIZE): 138 before, write_data, after = self._read_write_data(offset) 139 140 if before != after: 141 # Set the data back to the original value before failing 142 self._write_eeprom(offset, before) 143 144 raise error.TestFail('EEPROM data changed with write protect ' 145 'enabled. Offset %d' % (offset)) 146 147 return True 148 149 def check_eeprom_without_write_protected(self): 150 """Checks that CBI EEPROM can be written to when WP is de-asserted""" 151 self.set_hardware_write_protect(False) 152 offset = 0 153 154 if self._wp_is_decoupled: 155 # When the CBI WP is decoupled from the main system write protect, 156 # the EC drives a latch which sets the CBI WP. This latch is only 157 # reset when EC_RST_ODL is asserted. Since the WP has changed 158 # above, toggle EC_RST_ODL in order to clear this latch. 159 logging.info( 160 "CBI WP is EC driven, resetting EC before continuing...") 161 self._reset_ec_and_wait_up() 162 163 for offset in range(0, self.MAX_BYTES, self.PAGE_SIZE): 164 before, write_data, after = self._read_write_data(offset) 165 166 if write_data != after: 167 raise error.TestFail('EEPROM did not update with write protect ' 168 'disabled. Offset %d' % (offset)) 169 170 # Set the data back to the original value 171 self._write_eeprom(offset, before) 172 173 return True 174 175 def cleanup(self): 176 # Make sure to remove EC SW WP since we enabled it when testing 177 if self._wp_is_decoupled: 178 logging.debug("Disabling EC HW & SW WP...") 179 self.set_hardware_write_protect(False) 180 self._reset_ec_and_wait_up() 181 cmd = 'flashrom -p ec --wp-disable' 182 self.faft_client.system.run_shell_command(cmd) 183 return super(firmware_ECCbiEeprom, self).cleanup() 184 185 def run_once(self): 186 """Execute the main body of the test.""" 187 188 logging.info("Checking CBI EEPROM with write protect off...") 189 self.check_eeprom_without_write_protected() 190 191 logging.info("Checking CBI EEPROM with write protect on...") 192 self.check_eeprom_write_protected() 193