• Home
  • Line#
  • Scopes#
  • Navigate#
  • Raw
  • Download
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
5import os
6import logging
7
8from autotest_lib.client.common_lib import error
9from autotest_lib.client.common_lib import utils
10from autotest_lib.server.cros.faft.firmware_test import FirmwareTest
11
12TARGET_BIOS = 'host_firmware'
13TARGET_EC = 'ec_firmware'
14
15FMAP_AREA_NAMES = [
16    'name',
17    'offset',
18    'size'
19]
20
21EXPECTED_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
43EXPECTED_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
55class 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