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 79 def run_cmd(self, command): 80 """ 81 Log and execute command and return the output. 82 83 @param command: Command to executeon device. 84 @returns the output of command. 85 86 """ 87 logging.info('Execute %s', command) 88 output = self.faft_client.system.run_shell_command_get_output(command) 89 logging.info('Output %s', output) 90 return output 91 92 def get_areas(self): 93 """Get a list of dicts containing area names, offsets, and sizes 94 per device. 95 96 It fetches the FMap data from the active firmware via mosys. 97 Stores the result in the appropriate _TARGET_AREA. 98 """ 99 lines = self.run_cmd("mosys eeprom map") 100 101 # The above output is formatted as: 102 # name1 offset1 size1 103 # name2 offset2 size2 104 # ... 105 # Convert it to a list of dicts like: 106 # [{'name': name1, 'offset': offset1, 'size': size1}, 107 # {'name': name2, 'offset': offset2, 'size': size2}, ...] 108 for line in lines: 109 row = map(lambda s:s.strip(), line.split('|')) 110 self._TARGET_AREA[row[0]].append( 111 dict(zip(FMAP_AREA_NAMES, [row[1], row[2], row[3]]))) 112 113 114 def _is_bounded(self, region, bounds): 115 """Is the given region bounded by the given bounds?""" 116 return ((bounds[0] <= region[0] < bounds[1]) and 117 (bounds[0] < region[1] <= bounds[1])) 118 119 120 def _is_overlapping(self, region1, region2): 121 """Is the given region1 overlapping region2?""" 122 return (min(region1[1], region2[1]) > max(region1[0], region2[0])) 123 124 125 def check_section(self): 126 """Check RW_SECTION_[AB] and RW_LEGACY. 127 128 1- check RW_SECTION_[AB] exist, non-zero, same size 129 2- RW_LEGACY exist and > 1MB in size 130 """ 131 # Parse map into dictionary. 132 bios = {} 133 for e in self._TARGET_AREA[TARGET_BIOS]: 134 bios[e['name']] = {'offset': e['offset'], 'size': e['size']} 135 succeed = True 136 # Check RW_SECTION_[AB] sections. 137 if 'RW_SECTION_A' not in bios: 138 succeed = False 139 logging.error('Missing RW_SECTION_A section in FMAP') 140 elif 'RW_SECTION_B' not in bios: 141 succeed = False 142 logging.error('Missing RW_SECTION_B section in FMAP') 143 else: 144 if bios['RW_SECTION_A']['size'] != bios['RW_SECTION_B']['size']: 145 succeed = False 146 logging.error('RW_SECTION_A size != RW_SECTION_B size') 147 if (bios['RW_SECTION_A']['size'] == 0 148 or bios['RW_SECTION_B']['size'] == 0): 149 succeed = False 150 logging.error('RW_SECTION_A size or RW_SECTION_B size == 0') 151 # Check RW_LEGACY section. 152 if 'RW_LEGACY' not in bios: 153 succeed = False 154 logging.error('Missing RW_LEGACY section in FMAP') 155 else: 156 if bios['RW_LEGACY']['size'] < 1024*1024: 157 succeed = False 158 logging.error('RW_LEGACY size is < 1M') 159 if not succeed: 160 raise error.TestFail('SECTION check failed.') 161 162 163 def check_areas(self, areas, expected_tree, bounds=None): 164 """Check the given area list met the hierarchy of the expected_tree. 165 166 It checks all areas in the expected tree are existed and non-zero sized. 167 It checks all areas in sub-trees are bounded by the region of the root 168 node. It also checks all areas in child nodes are mutually exclusive. 169 170 @param areas: A list of dicts containing area names, offsets, and sizes. 171 @param expected_tree: A hierarchy dict of the expected FMap tree. 172 @param bounds: The boards that all areas in the expect_tree are bounded. 173 If None, ignore the bounds check. 174 175 >>> f = FMap() 176 >>> a = [{'name': 'FOO', 'offset': 100, 'size': '200'}, 177 ... {'name': 'BAR', 'offset': 100, 'size': '50'}, 178 ... {'name': 'ZEROSIZED', 'offset': 150, 'size': '0'}, 179 ... {'name': 'OUTSIDE', 'offset': 50, 'size': '50'}] 180 ... {'name': 'OVERLAP', 'offset': 120, 'size': '50'}, 181 >>> f.check_areas(a, {'FOO': {}}) 182 True 183 >>> f.check_areas(a, {'NOTEXISTED': {}}) 184 False 185 >>> f.check_areas(a, {'ZEROSIZED': {}}) 186 False 187 >>> f.check_areas(a, {'BAR': {}, 'OVERLAP': {}}) 188 False 189 >>> f.check_areas(a, {'FOO': {}, 'BAR': {}}) 190 False 191 >>> f.check_areas(a, {'FOO': {}, 'OUTSIDE': {}}) 192 True 193 >>> f.check_areas(a, {'FOO': {'BAR': {}}}) 194 True 195 >>> f.check_areas(a, {'FOO': {'OUTSIDE': {}}}) 196 False 197 >>> f.check_areas(a, {'FOO': {'NOTEXISTED': {}}}) 198 False 199 >>> f.check_areas(a, {'FOO': {'ZEROSIZED': {}}}) 200 False 201 """ 202 203 succeed = True 204 checked_regions = [] 205 for branch in expected_tree: 206 area = next((a for a in areas if a['name'] == branch), None) 207 if not area: 208 logging.error("The area %s is not existed.", branch) 209 succeed = False 210 continue 211 region = [int(area['offset'], 16), 212 int(area['offset'], 16) + int(area['size'], 16)] 213 if int(area['size'], 16) == 0: 214 logging.error("The area %s is zero-sized.", branch) 215 succeed = False 216 elif bounds and not self._is_bounded(region, bounds): 217 logging.error("The region %s [%d, %d) is out of the bounds " 218 "[%d, %d).", branch, region[0], region[1], 219 bounds[0], bounds[1]) 220 succeed = False 221 elif any(r for r in checked_regions if self._is_overlapping( 222 region, r)): 223 logging.error("The area %s is overlapping others.", branch) 224 succeed = False 225 elif not self.check_areas(areas, expected_tree[branch], region): 226 succeed = False 227 checked_regions.append(region) 228 return succeed 229 230 231 def run_once(self): 232 self.get_areas() 233 234 for key in self._TARGET_AREA.keys(): 235 if (self._TARGET_AREA[key] and 236 not self.check_areas(self._TARGET_AREA[key], 237 self._EXPECTED_FMAP_TREE[key])): 238 raise error.TestFail("%s FMap is not qualified.", key) 239 self.check_section() 240