xref: /aosp_15_r20/external/autotest/server/site_tests/firmware_ECCbiEeprom/firmware_ECCbiEeprom.py (revision 9c5db1993ded3edbeafc8092d69fe5de2ee02df7)
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