Home | History | Annotate | Download | only in firmware_FMap
      1 # Copyright (c) 2013 The Chromium OS Authors. All rights reserved.
      2 # Use of this source code is governed by a BSD-style license that can be
      3 # found in the LICENSE file.
      4 
      5 import os
      6 import logging
      7 
      8 from autotest_lib.client.common_lib import error
      9 from autotest_lib.client.common_lib import utils
     10 from autotest_lib.server.cros.faft.firmware_test import FirmwareTest
     11 
     12 TARGET_BIOS = 'host_firmware'
     13 TARGET_EC = 'ec_firmware'
     14 
     15 FMAP_AREA_NAMES = [
     16     'name',
     17     'offset',
     18     'size'
     19 ]
     20 
     21 EXPECTED_FMAP_TREE_BIOS = {
     22   'WP_RO': {
     23     'RO_SECTION': {
     24       'FMAP': {},
     25       'GBB': {},
     26       'RO_FRID': {},
     27     },
     28     'RO_VPD': {},
     29   },
     30   'RW_SECTION_A': {
     31     'VBLOCK_A': {},
     32     'FW_MAIN_A': {},
     33     'RW_FWID_A': {},
     34   },
     35   'RW_SECTION_B': {
     36     'VBLOCK_B': {},
     37     'FW_MAIN_B': {},
     38     'RW_FWID_B': {},
     39   },
     40   'RW_VPD': {},
     41 }
     42 
     43 EXPECTED_FMAP_TREE_EC = {
     44   'WP_RO': {
     45     'EC_RO': {
     46       'FMAP': {},
     47       'RO_FRID': {},
     48     },
     49   },
     50   'EC_RW': {
     51     'RW_FWID': {},
     52   },
     53 }
     54 
     55 class firmware_FMap(FirmwareTest):
     56     """Provides access to firmware FMap"""
     57 
     58     _TARGET_AREA = {
     59         TARGET_BIOS: [],
     60         TARGET_EC: [],
     61     }
     62 
     63     _EXPECTED_FMAP_TREE = {
     64         TARGET_BIOS: EXPECTED_FMAP_TREE_BIOS,
     65         TARGET_EC: EXPECTED_FMAP_TREE_EC,
     66     }
     67 
     68     """Client-side FMap test.
     69 
     70     This test checks the active BIOS and EC firmware contains the required
     71     FMap areas and verifies their hierarchies. It relies on flashrom to dump
     72     the active BIOS and EC firmware and dump_fmap to decode them.
     73     """
     74     version = 1
     75 
     76     def initialize(self, host, cmdline_args, dev_mode=False):
     77         super(firmware_FMap, self).initialize(host, cmdline_args)
     78         self.switcher.setup_mode('dev' if dev_mode else 'normal')
     79 
     80     def run_cmd(self, command):
     81         """
     82         Log and execute command and return the output.
     83 
     84         @param command: Command to executeon device.
     85         @returns the output of command.
     86 
     87         """
     88         logging.info('Execute %s', command)
     89         output = self.faft_client.system.run_shell_command_get_output(command)
     90         logging.info('Output %s', output)
     91         return output
     92 
     93     def get_areas(self):
     94         """Get a list of dicts containing area names, offsets, and sizes
     95         per device.
     96 
     97         It fetches the FMap data from the active firmware via mosys.
     98         Stores the result in the appropriate _TARGET_AREA.
     99         """
    100         lines = self.run_cmd("mosys eeprom map")
    101 
    102         # The above output is formatted as:
    103         # name1 offset1 size1
    104         # name2 offset2 size2
    105         # ...
    106         # Convert it to a list of dicts like:
    107         # [{'name': name1, 'offset': offset1, 'size': size1},
    108         #  {'name': name2, 'offset': offset2, 'size': size2}, ...]
    109         for line in lines:
    110             row = map(lambda s:s.strip(), line.split('|'))
    111             self._TARGET_AREA[row[0]].append(
    112                 dict(zip(FMAP_AREA_NAMES, [row[1], row[2], row[3]])))
    113 
    114 
    115     def _is_bounded(self, region, bounds):
    116         """Is the given region bounded by the given bounds?"""
    117         return ((bounds[0] <= region[0] < bounds[1]) and
    118                 (bounds[0] < region[1] <= bounds[1]))
    119 
    120 
    121     def _is_overlapping(self, region1, region2):
    122         """Is the given region1 overlapping region2?"""
    123         return (min(region1[1], region2[1]) > max(region1[0], region2[0]))
    124 
    125 
    126     def check_section(self):
    127         """Check RW_SECTION_[AB], RW_LEGACY and SMMSTORE.
    128 
    129         1- check RW_SECTION_[AB] exist, non-zero, same size
    130         2- RW_LEGACY exists and >= 1MB in size
    131         3- optionally check SMMSTORE exists and >= 256KB in size
    132         """
    133         # Parse map into dictionary.
    134         bios = {}
    135         for e in self._TARGET_AREA[TARGET_BIOS]:
    136            bios[e['name']] = {'offset': e['offset'], 'size': e['size']}
    137         succeed = True
    138         # Check RW_SECTION_[AB] sections.
    139         if 'RW_SECTION_A' not in bios:
    140             succeed = False
    141             logging.error('Missing RW_SECTION_A section in FMAP')
    142         elif 'RW_SECTION_B' not in bios:
    143             succeed = False
    144             logging.error('Missing RW_SECTION_B section in FMAP')
    145         else:
    146             if bios['RW_SECTION_A']['size'] != bios['RW_SECTION_B']['size']:
    147                 succeed = False
    148                 logging.error('RW_SECTION_A size != RW_SECTION_B size')
    149             if (int(bios['RW_SECTION_A']['size'], 16) == 0
    150                 or int(bios['RW_SECTION_B']['size'], 16) == 0):
    151                 succeed = False
    152                 logging.error('RW_SECTION_A size or RW_SECTION_B size == 0')
    153         # Check RW_LEGACY section.
    154         if 'RW_LEGACY' not in bios:
    155             succeed = False
    156             logging.error('Missing RW_LEGACY section in FMAP')
    157         else:
    158             if int(bios['RW_LEGACY']['size'], 16) < 1024*1024:
    159                 succeed = False
    160                 logging.error('RW_LEGACY size is < 1M')
    161         # Check SMMSTORE section.
    162         if self.faft_config.smm_store and 'x86' in self.run_cmd('uname -m')[0]:
    163             if 'SMMSTORE' not in bios:
    164                 succeed = False
    165                 logging.error('Missing SMMSTORE section in FMAP')
    166             else:
    167                 if int(bios['SMMSTORE']['size'], 16) < 256*1024:
    168                     succeed = False
    169                     logging.error('SMMSTORE size is < 256KB')
    170 
    171         if not succeed:
    172             raise error.TestFail('SECTION check failed.')
    173 
    174 
    175     def check_areas(self, areas, expected_tree, bounds=None):
    176         """Check the given area list met the hierarchy of the expected_tree.
    177 
    178         It checks all areas in the expected tree are existed and non-zero sized.
    179         It checks all areas in sub-trees are bounded by the region of the root
    180         node. It also checks all areas in child nodes are mutually exclusive.
    181 
    182         @param areas: A list of dicts containing area names, offsets, and sizes.
    183         @param expected_tree: A hierarchy dict of the expected FMap tree.
    184         @param bounds: The boards that all areas in the expect_tree are bounded.
    185                        If None, ignore the bounds check.
    186 
    187         >>> f = FMap()
    188         >>> a = [{'name': 'FOO', 'offset': 100, 'size': '200'},
    189         ...      {'name': 'BAR', 'offset': 100, 'size': '50'},
    190         ...      {'name': 'ZEROSIZED', 'offset': 150, 'size': '0'},
    191         ...      {'name': 'OUTSIDE', 'offset': 50, 'size': '50'}]
    192         ...      {'name': 'OVERLAP', 'offset': 120, 'size': '50'},
    193         >>> f.check_areas(a, {'FOO': {}})
    194         True
    195         >>> f.check_areas(a, {'NOTEXISTED': {}})
    196         False
    197         >>> f.check_areas(a, {'ZEROSIZED': {}})
    198         False
    199         >>> f.check_areas(a, {'BAR': {}, 'OVERLAP': {}})
    200         False
    201         >>> f.check_areas(a, {'FOO': {}, 'BAR': {}})
    202         False
    203         >>> f.check_areas(a, {'FOO': {}, 'OUTSIDE': {}})
    204         True
    205         >>> f.check_areas(a, {'FOO': {'BAR': {}}})
    206         True
    207         >>> f.check_areas(a, {'FOO': {'OUTSIDE': {}}})
    208         False
    209         >>> f.check_areas(a, {'FOO': {'NOTEXISTED': {}}})
    210         False
    211         >>> f.check_areas(a, {'FOO': {'ZEROSIZED': {}}})
    212         False
    213         """
    214 
    215         succeed = True
    216         checked_regions = []
    217         for branch in expected_tree:
    218             area = next((a for a in areas if a['name'] == branch), None)
    219             if not area:
    220                 logging.error("The area %s is not existed.", branch)
    221                 succeed = False
    222                 continue
    223             region = [int(area['offset'], 16),
    224                       int(area['offset'], 16) + int(area['size'], 16)]
    225             if int(area['size'], 16) == 0:
    226                 logging.error("The area %s is zero-sized.", branch)
    227                 succeed = False
    228             elif bounds and not self._is_bounded(region, bounds):
    229                 logging.error("The region %s [%d, %d) is out of the bounds "
    230                               "[%d, %d).", branch, region[0], region[1],
    231                               bounds[0], bounds[1])
    232                 succeed = False
    233             elif any(r for r in checked_regions if self._is_overlapping(
    234                     region, r)):
    235                 logging.error("The area %s is overlapping others.", branch)
    236                 succeed = False
    237             elif not self.check_areas(areas, expected_tree[branch], region):
    238                 succeed = False
    239             checked_regions.append(region)
    240         return succeed
    241 
    242 
    243     def run_once(self):
    244         self.get_areas()
    245 
    246         for key in self._TARGET_AREA.keys():
    247             if (self._TARGET_AREA[key] and
    248                     not self.check_areas(self._TARGET_AREA[key],
    249                                          self._EXPECTED_FMAP_TREE[key])):
    250                 raise error.TestFail("%s FMap is not qualified.", key)
    251         self.check_section()
    252