| # Copyright 2018 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 os |
| import tempfile |
| |
| from autotest_lib.client.common_lib import error |
| from autotest_lib.server.cros.faft.firmware_test import FirmwareTest |
| |
| |
| class platform_FlashErasers(FirmwareTest): |
| """ |
| Test that various erase functions work correctly by calling flashrom to |
| erase/write blocks of different sizes. |
| """ |
| version = 1 |
| |
| def run_cmd(self, command, checkfor=''): |
| """ |
| Log and execute command and return the output. |
| |
| @param command: Command to execute on device. |
| @param checkfor: If not emmpty, fail test if checkfor not in output. |
| @returns the output of command as a list of strings. |
| """ |
| command = command + ' 2>&1' |
| logging.info('Execute %s', command) |
| output = self.faft_client.system.run_shell_command_get_output(command) |
| logging.info('Output >>> %s <<<', output) |
| if checkfor and checkfor not in '\n'.join(output): |
| raise error.TestFail('Expect %s in output of %s' % |
| (checkfor, '\n'.join(output))) |
| return output |
| |
| def _get_section(self, bios, section): |
| """Return start address and size of an fmap section. |
| |
| @param bios: string, bios file name to retrieve fmap from |
| @param section: string, section name to look for |
| |
| @return tuple of ints for start and size of the section. |
| """ |
| # Store temp results in the attributes dictionary; when the expected |
| # section name is found in the 'area_name:' field, the offset and size |
| # of the section would be stored in this dictionary. |
| attributes = {} |
| for line in [x.strip() for x in self.run_cmd('dump_fmap %s' % bios)]: |
| if not line: |
| continue |
| tokens = line.split() |
| if tokens[0] == 'area_name:' and tokens[1] == section: |
| return (int(attributes['area_offset:'], 16), |
| int(attributes['area_size:'], 16)) |
| |
| if len(tokens) > 1: |
| attributes[tokens[0]] = tokens[1] |
| |
| raise error.TestFail('Could not find section %s in the fmap' % section) |
| |
| def _create_test_blob(self, blob_name, blob_size): |
| """On the DUT create a blob containing 0xff bytes of the requested size. |
| |
| @param blob_name: string, name of the DUT file to save the blob in |
| @param blob_size: integer, size of the blob to prepare |
| """ |
| test_blob_data = ('%c' % 0xff) * blob_size |
| |
| # Save it into a local file. |
| handle, local_file = tempfile.mkstemp() |
| |
| try: |
| os.write(handle, test_blob_data) |
| os.close(handle) |
| |
| # Copy the local file to the DUT. |
| self._client.send_file(local_file, blob_name) |
| |
| finally: |
| # Delete local file. |
| os.remove(local_file) |
| |
| def run_once(self, dev_mode=True): |
| """Main method implementing test logic.""" |
| |
| # Find out which AP firmware section is active to determine which AP |
| # firmware section could be overwritten. |
| active_fw = self.run_cmd('crossystem mainfw_act')[0] |
| if active_fw == 'A': |
| section = 'RW_SECTION_B' |
| elif active_fw == 'B': |
| section = 'RW_SECTION_A' |
| else: |
| raise error.TestFail('Unexpected active fw %s' % active_fw) |
| |
| dut_work_path = self.faft_client.updater.get_work_path() |
| # Sizes to try to erase. |
| test_sizes = (4096, 4096 * 2, 4096 * 4, 4096 * 8, 4096 * 16) |
| |
| # Image read from the AP firmware flash chip. |
| bios_image = os.path.join(dut_work_path, 'bios_image') |
| self.run_cmd('flashrom -r %s' % bios_image) |
| |
| # A blob of all ones to paste into the image. |
| test_blob = os.path.join(dut_work_path, 'test_blob') |
| self._create_test_blob(test_blob, max(test_sizes)) |
| |
| # The file to store the 'corrupted' image with all ones pasted. |
| junk_image = os.path.join(dut_work_path, 'junk_image') |
| |
| # Find in fmap the AP firmware section which can be overwritten. |
| start, size = self._get_section(bios_image, section) |
| logging.info('Active firmware %s, alternative at %#x:%#x', active_fw, |
| start, start + size -1) |
| |
| # Command to paste the all ones blob into the corrupted image. |
| dd_template = 'dd if="%s" of="%s" bs=1 conv=notrunc seek="%d"' % ( |
| test_blob, junk_image, start) |
| |
| for test_size in test_sizes: |
| |
| logging.info('Verifying section of size %d', test_size) |
| |
| self.run_cmd('cp %s %s' % (bios_image, junk_image)) |
| |
| # Set section in the 'junk' image to 'all erased' |
| dd_cmd = dd_template + ' count="%d"' % test_size |
| self.run_cmd(dd_cmd) |
| |
| # Now program the corrupted image, this would involve erasing the |
| # section of test_size bytes. |
| self.run_cmd('flashrom -w %s --diff %s --fast-verify' % |
| (junk_image, bios_image)) |
| |
| # Now restore the image. |
| self.run_cmd('flashrom -w %s --diff %s' % (bios_image, junk_image)) |