blob: 31e4fb0492e5350def946806804e239f1e5f72f4 [file] [log] [blame]
# 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)
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 = 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_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)