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