| # Copyright (c) 2013 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 os |
| import logging |
| |
| from autotest_lib.client.common_lib import error |
| from autotest_lib.client.common_lib import utils |
| from autotest_lib.server.cros.faft.firmware_test import FirmwareTest |
| |
| TARGET_BIOS = 'host_firmware' |
| TARGET_EC = 'ec_firmware' |
| |
| FMAP_AREA_NAMES = [ |
| 'name', |
| 'offset', |
| 'size' |
| ] |
| |
| EXPECTED_FMAP_TREE_BIOS = { |
| 'WP_RO': { |
| 'RO_SECTION': { |
| 'FMAP': {}, |
| 'GBB': {}, |
| 'RO_FRID': {}, |
| }, |
| 'RO_VPD': {}, |
| }, |
| 'RW_SECTION_A': { |
| 'VBLOCK_A': {}, |
| 'FW_MAIN_A': {}, |
| 'RW_FWID_A': {}, |
| }, |
| 'RW_SECTION_B': { |
| 'VBLOCK_B': {}, |
| 'FW_MAIN_B': {}, |
| 'RW_FWID_B': {}, |
| }, |
| 'RW_VPD': {}, |
| } |
| |
| EXPECTED_FMAP_TREE_EC = { |
| 'WP_RO': { |
| 'EC_RO': { |
| 'FMAP': {}, |
| 'RO_FRID': {}, |
| }, |
| }, |
| 'EC_RW': { |
| 'RW_FWID': {}, |
| }, |
| } |
| |
| class firmware_FMap(FirmwareTest): |
| """Provides access to firmware FMap""" |
| |
| _TARGET_AREA = { |
| TARGET_BIOS: [], |
| TARGET_EC: [], |
| } |
| |
| _EXPECTED_FMAP_TREE = { |
| TARGET_BIOS: EXPECTED_FMAP_TREE_BIOS, |
| TARGET_EC: EXPECTED_FMAP_TREE_EC, |
| } |
| |
| """Client-side FMap test. |
| |
| This test checks the active BIOS and EC firmware contains the required |
| FMap areas and verifies their hierarchies. It relies on flashrom to dump |
| the active BIOS and EC firmware and dump_fmap to decode them. |
| """ |
| version = 1 |
| |
| def initialize(self, host, cmdline_args, dev_mode=False): |
| super(firmware_FMap, self).initialize(host, cmdline_args) |
| self.switcher.setup_mode('dev' if dev_mode else 'normal') |
| |
| def run_cmd(self, command): |
| """ |
| Log and execute command and return the output. |
| |
| @param command: Command to executeon device. |
| @returns the output of command. |
| |
| """ |
| logging.info('Execute %s', command) |
| output = self.faft_client.system.run_shell_command_get_output(command) |
| logging.info('Output %s', output) |
| return output |
| |
| def get_areas(self): |
| """Get a list of dicts containing area names, offsets, and sizes |
| per device. |
| |
| It fetches the FMap data from the active firmware via mosys. |
| Stores the result in the appropriate _TARGET_AREA. |
| """ |
| lines = self.run_cmd("mosys eeprom map") |
| |
| # The above output is formatted as: |
| # name1 offset1 size1 |
| # name2 offset2 size2 |
| # ... |
| # Convert it to a list of dicts like: |
| # [{'name': name1, 'offset': offset1, 'size': size1}, |
| # {'name': name2, 'offset': offset2, 'size': size2}, ...] |
| for line in lines: |
| row = map(lambda s:s.strip(), line.split('|')) |
| self._TARGET_AREA[row[0]].append( |
| dict(zip(FMAP_AREA_NAMES, [row[1], row[2], row[3]]))) |
| |
| |
| def _is_bounded(self, region, bounds): |
| """Is the given region bounded by the given bounds?""" |
| return ((bounds[0] <= region[0] < bounds[1]) and |
| (bounds[0] < region[1] <= bounds[1])) |
| |
| |
| def _is_overlapping(self, region1, region2): |
| """Is the given region1 overlapping region2?""" |
| return (min(region1[1], region2[1]) > max(region1[0], region2[0])) |
| |
| |
| def check_section(self): |
| """Check RW_SECTION_[AB] and RW_LEGACY. |
| |
| 1- check RW_SECTION_[AB] exist, non-zero, same size |
| 2- RW_LEGACY exist and > 1MB in size |
| """ |
| # Parse map into dictionary. |
| bios = {} |
| for e in self._TARGET_AREA[TARGET_BIOS]: |
| bios[e['name']] = {'offset': e['offset'], 'size': e['size']} |
| succeed = True |
| # Check RW_SECTION_[AB] sections. |
| if 'RW_SECTION_A' not in bios: |
| succeed = False |
| logging.error('Missing RW_SECTION_A section in FMAP') |
| elif 'RW_SECTION_B' not in bios: |
| succeed = False |
| logging.error('Missing RW_SECTION_B section in FMAP') |
| else: |
| if bios['RW_SECTION_A']['size'] != bios['RW_SECTION_B']['size']: |
| succeed = False |
| logging.error('RW_SECTION_A size != RW_SECTION_B size') |
| if (bios['RW_SECTION_A']['size'] == 0 |
| or bios['RW_SECTION_B']['size'] == 0): |
| succeed = False |
| logging.error('RW_SECTION_A size or RW_SECTION_B size == 0') |
| # Check RW_LEGACY section. |
| if 'RW_LEGACY' not in bios: |
| succeed = False |
| logging.error('Missing RW_LEGACY section in FMAP') |
| else: |
| if bios['RW_LEGACY']['size'] < 1024*1024: |
| succeed = False |
| logging.error('RW_LEGACY size is < 1M') |
| if not succeed: |
| raise error.TestFail('SECTION check failed.') |
| |
| |
| def check_areas(self, areas, expected_tree, bounds=None): |
| """Check the given area list met the hierarchy of the expected_tree. |
| |
| It checks all areas in the expected tree are existed and non-zero sized. |
| It checks all areas in sub-trees are bounded by the region of the root |
| node. It also checks all areas in child nodes are mutually exclusive. |
| |
| @param areas: A list of dicts containing area names, offsets, and sizes. |
| @param expected_tree: A hierarchy dict of the expected FMap tree. |
| @param bounds: The boards that all areas in the expect_tree are bounded. |
| If None, ignore the bounds check. |
| |
| >>> f = FMap() |
| >>> a = [{'name': 'FOO', 'offset': 100, 'size': '200'}, |
| ... {'name': 'BAR', 'offset': 100, 'size': '50'}, |
| ... {'name': 'ZEROSIZED', 'offset': 150, 'size': '0'}, |
| ... {'name': 'OUTSIDE', 'offset': 50, 'size': '50'}] |
| ... {'name': 'OVERLAP', 'offset': 120, 'size': '50'}, |
| >>> f.check_areas(a, {'FOO': {}}) |
| True |
| >>> f.check_areas(a, {'NOTEXISTED': {}}) |
| False |
| >>> f.check_areas(a, {'ZEROSIZED': {}}) |
| False |
| >>> f.check_areas(a, {'BAR': {}, 'OVERLAP': {}}) |
| False |
| >>> f.check_areas(a, {'FOO': {}, 'BAR': {}}) |
| False |
| >>> f.check_areas(a, {'FOO': {}, 'OUTSIDE': {}}) |
| True |
| >>> f.check_areas(a, {'FOO': {'BAR': {}}}) |
| True |
| >>> f.check_areas(a, {'FOO': {'OUTSIDE': {}}}) |
| False |
| >>> f.check_areas(a, {'FOO': {'NOTEXISTED': {}}}) |
| False |
| >>> f.check_areas(a, {'FOO': {'ZEROSIZED': {}}}) |
| False |
| """ |
| |
| succeed = True |
| checked_regions = [] |
| for branch in expected_tree: |
| area = next((a for a in areas if a['name'] == branch), None) |
| if not area: |
| logging.error("The area %s is not existed.", branch) |
| succeed = False |
| continue |
| region = [int(area['offset'], 16), |
| int(area['offset'], 16) + int(area['size'], 16)] |
| if int(area['size'], 16) == 0: |
| logging.error("The area %s is zero-sized.", branch) |
| succeed = False |
| elif bounds and not self._is_bounded(region, bounds): |
| logging.error("The region %s [%d, %d) is out of the bounds " |
| "[%d, %d).", branch, region[0], region[1], |
| bounds[0], bounds[1]) |
| succeed = False |
| elif any(r for r in checked_regions if self._is_overlapping( |
| region, r)): |
| logging.error("The area %s is overlapping others.", branch) |
| succeed = False |
| elif not self.check_areas(areas, expected_tree[branch], region): |
| succeed = False |
| checked_regions.append(region) |
| return succeed |
| |
| |
| def run_once(self): |
| self.get_areas() |
| |
| for key in self._TARGET_AREA.keys(): |
| if (self._TARGET_AREA[key] and |
| not self.check_areas(self._TARGET_AREA[key], |
| self._EXPECTED_FMAP_TREE[key])): |
| raise error.TestFail("%s FMap is not qualified.", key) |
| self.check_section() |