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 87 def run_cmd(self, command): 88 """ 89 Log and execute command and return the output. 90 91 @param command: Command to executeon device. 92 @returns the output of command. 93 94 """ 95 logging.info('Execute %s', command) 96 output = self.faft_client.system.run_shell_command_get_output(command) 97 logging.info('Output %s', output) 98 return output 99 100 def _has_target(self, name): 101 """Return True if flashrom supports the programmer specified.""" 102 return self.faft_client.system.run_shell_command_get_status( 103 'flashrom -p %s' % name) == 0 104 105 def get_areas(self): 106 """Get a list of dicts containing area names, offsets, and sizes 107 per device. 108 109 It fetches the FMap data from the active firmware via flashrom. 110 Stores the result in the appropriate _TARGET_AREA. 111 """ 112 for target in self._TARGET_AREA: 113 if not self._has_target(target): 114 continue 115 tmpdir = self.faft_client.system.create_temp_dir('flashrom_') 116 fmap = os.path.join(tmpdir, 'fmap.bin') 117 self.run_cmd( 118 'flashrom -p %s -r -i FMAP:%s' % (target, fmap)) 119 lines = self.run_cmd('dump_fmap -p %s' % fmap) 120 # Change the expected FMAP Tree if separate CBFS is used for CSE RW 121 command = "dump_fmap -F %s | grep ME_RW_A" % fmap 122 if (target in TARGET_BIOS) and self.run_cmd(command): 123 self._EXPECTED_FMAP_TREE[target]['RW_SECTION_A'].update( 124 INTEL_CSE_RW_A) 125 self._EXPECTED_FMAP_TREE[target]['RW_SECTION_B'].update( 126 INTEL_CSE_RW_B) 127 logging.info("DUT uses INTEL CSE LITE FMAP Scheme") 128 129 self.faft_client.system.remove_dir(tmpdir) 130 131 # The above output is formatted as: 132 # name1 offset1 size1 133 # name2 offset2 size2 134 # ... 135 # Convert it to a list of dicts like: 136 # [{'name': name1, 'offset': offset1, 'size': size1}, 137 # {'name': name2, 'offset': offset2, 'size': size2}, ...] 138 for line in lines: 139 self._TARGET_AREA[target].append( 140 dict(zip(FMAP_AREA_NAMES, line.split()))) 141 142 def _is_bounded(self, region, bounds): 143 """Is the given region bounded by the given bounds?""" 144 return ((bounds[0] <= region[0] < bounds[1]) and 145 (bounds[0] < region[1] <= bounds[1])) 146 147 148 def _is_overlapping(self, region1, region2): 149 """Is the given region1 overlapping region2?""" 150 return (min(region1[1], region2[1]) > max(region1[0], region2[0])) 151 152 153 def check_section(self): 154 """Check RW_SECTION_[AB], RW_LEGACY and SMMSTORE. 155 156 1- check RW_SECTION_[AB] exist, non-zero, same size 157 2- RW_LEGACY exists and >= 1MB in size 158 3- optionally check SMMSTORE exists and >= 256KB in size 159 """ 160 # Parse map into dictionary. 161 bios = {} 162 for e in self._TARGET_AREA[TARGET_BIOS]: 163 bios[e['name']] = {'offset': e['offset'], 'size': e['size']} 164 succeed = True 165 # Check RW_SECTION_[AB] sections. 166 if 'RW_SECTION_A' not in bios: 167 succeed = False 168 logging.error('Missing RW_SECTION_A section in FMAP') 169 elif 'RW_SECTION_B' not in bios: 170 succeed = False 171 logging.error('Missing RW_SECTION_B section in FMAP') 172 else: 173 if bios['RW_SECTION_A']['size'] != bios['RW_SECTION_B']['size']: 174 succeed = False 175 logging.error('RW_SECTION_A size != RW_SECTION_B size') 176 if (int(bios['RW_SECTION_A']['size']) == 0 177 or int(bios['RW_SECTION_B']['size']) == 0): 178 succeed = False 179 logging.error('RW_SECTION_A size or RW_SECTION_B size == 0') 180 # Check RW_LEGACY section. 181 if 'RW_LEGACY' not in bios: 182 succeed = False 183 logging.error('Missing RW_LEGACY section in FMAP') 184 else: 185 if int(bios['RW_LEGACY']['size']) < 1024*1024: 186 succeed = False 187 logging.error('RW_LEGACY size is < 1M') 188 # Check SMMSTORE section. 189 if self.faft_config.smm_store and 'x86' in self.run_cmd('uname -m')[0]: 190 if 'SMMSTORE' not in bios: 191 succeed = False 192 logging.error('Missing SMMSTORE section in FMAP') 193 else: 194 if int(bios['SMMSTORE']['size']) < 256*1024: 195 succeed = False 196 logging.error('SMMSTORE size is < 256KB') 197 198 if not succeed: 199 raise error.TestFail('SECTION check failed.') 200 201 202 def check_areas(self, areas, expected_tree, bounds=None): 203 """Check the given area list met the hierarchy of the expected_tree. 204 205 It checks all areas in the expected tree are existed and non-zero sized. 206 It checks all areas in sub-trees are bounded by the region of the root 207 node. It also checks all areas in child nodes are mutually exclusive. 208 209 @param areas: A list of dicts containing area names, offsets, and sizes. 210 @param expected_tree: A hierarchy dict of the expected FMap tree. 211 @param bounds: The boards that all areas in the expect_tree are bounded. 212 If None, ignore the bounds check. 213 214 >>> f = FMap() 215 >>> a = [{'name': 'FOO', 'offset': 100, 'size': '200'}, 216 ... {'name': 'BAR', 'offset': 100, 'size': '50'}, 217 ... {'name': 'ZEROSIZED', 'offset': 150, 'size': '0'}, 218 ... {'name': 'OUTSIDE', 'offset': 50, 'size': '50'}] 219 ... {'name': 'OVERLAP', 'offset': 120, 'size': '50'}, 220 >>> f.check_areas(a, {'FOO': {}}) 221 True 222 >>> f.check_areas(a, {'NOTEXISTED': {}}) 223 False 224 >>> f.check_areas(a, {'ZEROSIZED': {}}) 225 False 226 >>> f.check_areas(a, {'BAR': {}, 'OVERLAP': {}}) 227 False 228 >>> f.check_areas(a, {'FOO': {}, 'BAR': {}}) 229 False 230 >>> f.check_areas(a, {'FOO': {}, 'OUTSIDE': {}}) 231 True 232 >>> f.check_areas(a, {'FOO': {'BAR': {}}}) 233 True 234 >>> f.check_areas(a, {'FOO': {'OUTSIDE': {}}}) 235 False 236 >>> f.check_areas(a, {'FOO': {'NOTEXISTED': {}}}) 237 False 238 >>> f.check_areas(a, {'FOO': {'ZEROSIZED': {}}}) 239 False 240 """ 241 242 succeed = True 243 checked_regions = [] 244 for branch in expected_tree: 245 area = next((a for a in areas if a['name'] == branch), None) 246 if not area: 247 logging.error("The area %s is not existed.", branch) 248 succeed = False 249 continue 250 region = [int(area['offset']), 251 int(area['offset']) + int(area['size'])] 252 if int(area['size']) == 0: 253 logging.error("The area %s is zero-sized.", branch) 254 succeed = False 255 elif bounds and not self._is_bounded(region, bounds): 256 logging.error("The region %s [%d, %d) is out of the bounds " 257 "[%d, %d).", branch, region[0], region[1], 258 bounds[0], bounds[1]) 259 succeed = False 260 elif any(r for r in checked_regions if self._is_overlapping( 261 region, r)): 262 logging.error("The area %s is overlapping others.", branch) 263 succeed = False 264 elif not self.check_areas(areas, expected_tree[branch], region): 265 succeed = False 266 checked_regions.append(region) 267 return succeed 268 269 270 def run_once(self): 271 """Runs a single iteration of the test.""" 272 self.get_areas() 273 274 for key in self._TARGET_AREA.keys(): 275 if (self._TARGET_AREA[key] and 276 not self.check_areas(self._TARGET_AREA[key], 277 self._EXPECTED_FMAP_TREE[key])): 278 raise error.TestFail("%s FMap is not qualified.", key) 279 self.check_section() 280