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