# 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], RW_LEGACY and SMMSTORE. 1- check RW_SECTION_[AB] exist, non-zero, same size 2- RW_LEGACY exists and >= 1MB in size 3- optionally check SMMSTORE exists and >= 256KB 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 (int(bios['RW_SECTION_A']['size'], 16) == 0 or int(bios['RW_SECTION_B']['size'], 16) == 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 int(bios['RW_LEGACY']['size'], 16) < 1024*1024: succeed = False logging.error('RW_LEGACY size is < 1M') # Check SMMSTORE section. if self.faft_config.smm_store and 'x86' in self.run_cmd('uname -m')[0]: if 'SMMSTORE' not in bios: succeed = False logging.error('Missing SMMSTORE section in FMAP') else: if int(bios['SMMSTORE']['size'], 16) < 256*1024: succeed = False logging.error('SMMSTORE size is < 256KB') 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()