• 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] and RW_LEGACY.
128
129        1- check RW_SECTION_[AB] exist, non-zero, same size
130        2- RW_LEGACY exist and > 1MB in size
131        """
132        # Parse map into dictionary.
133        bios = {}
134        for e in self._TARGET_AREA[TARGET_BIOS]:
135           bios[e['name']] = {'offset': e['offset'], 'size': e['size']}
136        succeed = True
137        # Check RW_SECTION_[AB] sections.
138        if 'RW_SECTION_A' not in bios:
139            succeed = False
140            logging.error('Missing RW_SECTION_A section in FMAP')
141        elif 'RW_SECTION_B' not in bios:
142            succeed = False
143            logging.error('Missing RW_SECTION_B section in FMAP')
144        else:
145            if bios['RW_SECTION_A']['size'] != bios['RW_SECTION_B']['size']:
146                succeed = False
147                logging.error('RW_SECTION_A size != RW_SECTION_B size')
148            if (bios['RW_SECTION_A']['size'] == 0
149                or bios['RW_SECTION_B']['size'] == 0):
150                succeed = False
151                logging.error('RW_SECTION_A size or RW_SECTION_B size == 0')
152        # Check RW_LEGACY section.
153        if 'RW_LEGACY' not in bios:
154            succeed = False
155            logging.error('Missing RW_LEGACY section in FMAP')
156        else:
157            if bios['RW_LEGACY']['size'] < 1024*1024:
158                succeed = False
159                logging.error('RW_LEGACY size is < 1M')
160        if not succeed:
161            raise error.TestFail('SECTION check failed.')
162
163
164    def check_areas(self, areas, expected_tree, bounds=None):
165        """Check the given area list met the hierarchy of the expected_tree.
166
167        It checks all areas in the expected tree are existed and non-zero sized.
168        It checks all areas in sub-trees are bounded by the region of the root
169        node. It also checks all areas in child nodes are mutually exclusive.
170
171        @param areas: A list of dicts containing area names, offsets, and sizes.
172        @param expected_tree: A hierarchy dict of the expected FMap tree.
173        @param bounds: The boards that all areas in the expect_tree are bounded.
174                       If None, ignore the bounds check.
175
176        >>> f = FMap()
177        >>> a = [{'name': 'FOO', 'offset': 100, 'size': '200'},
178        ...      {'name': 'BAR', 'offset': 100, 'size': '50'},
179        ...      {'name': 'ZEROSIZED', 'offset': 150, 'size': '0'},
180        ...      {'name': 'OUTSIDE', 'offset': 50, 'size': '50'}]
181        ...      {'name': 'OVERLAP', 'offset': 120, 'size': '50'},
182        >>> f.check_areas(a, {'FOO': {}})
183        True
184        >>> f.check_areas(a, {'NOTEXISTED': {}})
185        False
186        >>> f.check_areas(a, {'ZEROSIZED': {}})
187        False
188        >>> f.check_areas(a, {'BAR': {}, 'OVERLAP': {}})
189        False
190        >>> f.check_areas(a, {'FOO': {}, 'BAR': {}})
191        False
192        >>> f.check_areas(a, {'FOO': {}, 'OUTSIDE': {}})
193        True
194        >>> f.check_areas(a, {'FOO': {'BAR': {}}})
195        True
196        >>> f.check_areas(a, {'FOO': {'OUTSIDE': {}}})
197        False
198        >>> f.check_areas(a, {'FOO': {'NOTEXISTED': {}}})
199        False
200        >>> f.check_areas(a, {'FOO': {'ZEROSIZED': {}}})
201        False
202        """
203
204        succeed = True
205        checked_regions = []
206        for branch in expected_tree:
207            area = next((a for a in areas if a['name'] == branch), None)
208            if not area:
209                logging.error("The area %s is not existed.", branch)
210                succeed = False
211                continue
212            region = [int(area['offset'], 16),
213                      int(area['offset'], 16) + int(area['size'], 16)]
214            if int(area['size'], 16) == 0:
215                logging.error("The area %s is zero-sized.", branch)
216                succeed = False
217            elif bounds and not self._is_bounded(region, bounds):
218                logging.error("The region %s [%d, %d) is out of the bounds "
219                              "[%d, %d).", branch, region[0], region[1],
220                              bounds[0], bounds[1])
221                succeed = False
222            elif any(r for r in checked_regions if self._is_overlapping(
223                    region, r)):
224                logging.error("The area %s is overlapping others.", branch)
225                succeed = False
226            elif not self.check_areas(areas, expected_tree[branch], region):
227                succeed = False
228            checked_regions.append(region)
229        return succeed
230
231
232    def run_once(self):
233        self.get_areas()
234
235        for key in self._TARGET_AREA.keys():
236            if (self._TARGET_AREA[key] and
237                    not self.check_areas(self._TARGET_AREA[key],
238                                         self._EXPECTED_FMAP_TREE[key])):
239                raise error.TestFail("%s FMap is not qualified.", key)
240        self.check_section()
241