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