| # Copyright 2022 The Chromium OS Authors. All rights reserved. |
| # Use of this source code is governed by a BSD-style license that can be |
| # found in the LICENSE file. |
| |
| import logging |
| import re |
| import time |
| |
| from autotest_lib.client.common_lib import error |
| from autotest_lib.client.common_lib.cros import cr50_utils |
| from autotest_lib.server.cros.faft.cr50_test import Cr50Test |
| |
| |
| class firmware_GSCAPROV1Trigger(Cr50Test): |
| """Verify GSC response after triggering AP RO V1 verification.""" |
| version = 1 |
| |
| # This only verifies V1 output right now. |
| TEST_AP_RO_VER = 1 |
| |
| # DBG image has to be able to set the AP RO hash with the board id set. |
| MIN_DBG_VER = '1.6.100' |
| |
| VERIFICATION_PASSED = 1 |
| VERIFICATION_FAILED = 2 |
| |
| DIGEST_RE = r' digest ([0-9a-f]{64})' |
| CALCULATED_DIGEST_RE = 'Calculated' + DIGEST_RE |
| STORED_DIGEST_RE = 'Stored' + DIGEST_RE |
| |
| def initialize(self, host, cmdline_args, full_args={}): |
| """Initialize servo""" |
| self.ran_test = False |
| super(firmware_GSCAPROV1Trigger, |
| self).initialize(host, |
| cmdline_args, |
| full_args, |
| restore_cr50_image=True) |
| if not self.cr50.ap_ro_version_is_supported(self.TEST_AP_RO_VER): |
| raise error.TestNAError('GSC does not support AP RO v%s' % |
| self.TEST_AP_RO_VER) |
| |
| dbg_ver = cr50_utils.InstallImage(self.host, |
| self.get_saved_dbg_image_path(), |
| '/tmp/cr50.bin')[1][1] |
| if cr50_utils.GetNewestVersion(dbg_ver, |
| self.MIN_DBG_VER) == self.MIN_DBG_VER: |
| raise error.TestNAError('Update DBG image to 6.100 or newer.') |
| |
| def update_to_dbg_and_clear_hash(self): |
| """Clear the Hash.""" |
| # Make sure the AP is up before trying to update. |
| self.recover_dut() |
| self._retry_cr50_update(self._dbg_image_path, 3, False) |
| self.cr50.send_command('ap_ro_info erase') |
| time.sleep(3) |
| ap_ro_info = self.cr50.get_ap_ro_info() |
| logging.info(ap_ro_info) |
| if ap_ro_info['hash']: |
| raise error.TestError('Could not erase hash') |
| |
| def after_run_once(self): |
| """Reboot cr50 to recover the dut.""" |
| try: |
| self.recover_dut() |
| finally: |
| super(firmware_GSCAPROV1Trigger, self).after_run_once() |
| |
| def set_hash(self): |
| """Set the Hash.""" |
| self.recover_dut() |
| result = self.host.run('ap_ro_hash.py -v True GBB') |
| logging.info(result) |
| time.sleep(3) |
| ap_ro_info = self.cr50.get_ap_ro_info() |
| logging.info(ap_ro_info) |
| if not ap_ro_info['hash']: |
| raise error.TestError('Could not set hash %r' % result) |
| |
| def rollback_to_release_image(self): |
| """Update to the release image.""" |
| self._retry_cr50_update(self.get_saved_cr50_original_path(), |
| 3, |
| rollback=True) |
| logging.info(self.cr50.get_ap_ro_info()) |
| |
| def cleanup(self): |
| """Clear the AP RO hash.""" |
| try: |
| if not self.ran_test: |
| return |
| logging.info('Cleanup') |
| self.recover_dut() |
| self.update_to_dbg_and_clear_hash() |
| self.rollback_to_release_image() |
| finally: |
| super(firmware_GSCAPROV1Trigger, self).cleanup() |
| |
| def recover_dut(self): |
| """Reboot gsc to recover the dut.""" |
| logging.info('Recover DUT') |
| ap_ro_info = self.cr50.get_ap_ro_info() |
| logging.info(ap_ro_info) |
| if ap_ro_info['result'] != self.VERIFICATION_FAILED: |
| self._try_to_bring_dut_up() |
| return |
| time.sleep(3) |
| self.cr50.send_command('ccd testlab open') |
| time.sleep(3) |
| self.cr50.reboot() |
| time.sleep(self.faft_config.delay_reboot_to_ping) |
| logging.info(self.cr50.get_ap_ro_info()) |
| self._try_to_bring_dut_up() |
| self.cr50.send_command('ccd testlab open') |
| |
| def trigger_verification(self): |
| """Trigger verification.""" |
| try: |
| self.recover_dut() |
| result = self.host.run('gsctool -aB start', |
| ignore_timeout=True, |
| ignore_status=True, |
| timeout=20) |
| logging.info(result) |
| finally: |
| time.sleep(5) |
| ap_ro_info = self.cr50.get_ap_ro_info() |
| logging.info(ap_ro_info) |
| self.hash_results.append(ap_ro_info['result']) |
| self.servo.record_uart_capture() |
| |
| def run_once(self): |
| """Save hash and trigger verification""" |
| self.ran_test = True |
| self.hash_results = [] |
| # The DBG image can set the hash when the board id is saved. The release |
| # image can't. Set the hash with the DBG image, so the test doesn't need |
| # to erase the board id. This test verifies triggering AP RO |
| # verification. It's not about saving the hash. |
| self.update_to_dbg_and_clear_hash() |
| self.set_hash() |
| self.rollback_to_release_image() |
| # CCD has to be open to trigger verification. |
| self.fast_ccd_open(True) |
| |
| # Trigger verification multiple times. Make sure it doesn't fail or |
| # change. |
| self.trigger_verification() |
| self.trigger_verification() |
| self.trigger_verification() |
| self.trigger_verification() |
| |
| self.servo.record_uart_capture() |
| cr50_uart_file = self.servo.get_uart_logfile('cr50') |
| if not cr50_uart_file: |
| logging.info('No cr50 uart file') |
| return |
| with open(cr50_uart_file, 'r') as f: |
| contents = f.read() |
| |
| self.recover_dut() |
| |
| # GSC only prints calculated and stored hashes after AP RO verificaiton |
| # fails. These sets will be empty if verification passed every time. |
| calculated = set(re.findall(self.CALCULATED_DIGEST_RE, contents)) |
| stored = set(re.findall(self.STORED_DIGEST_RE, contents)) |
| logging.info('Stored: %r', stored) |
| logging.info('Calculated: %r', calculated) |
| logging.info('Results: %r', self.hash_results) |
| |
| if self.VERIFICATION_FAILED in self.hash_results: |
| raise error.TestFail( |
| 'Verification failed -- stored: %r calculated: %r' % |
| (stored, calculated)) |
| if len(calculated) > 1: |
| raise error.TestFail('Multiple calculated digests %r' % calculated) |
| # This shouldn't happen. Raise TestNA, so it's easy to see. |
| if self.VERIFICATION_PASSED not in self.hash_results: |
| raise error.TestNAError( |
| 'Verification Not Run -- stored: %r calculated: %r' % |
| (stored, calculated)) |
| |
| # TODO(b/218705748): change the hash and verify verification fails. |