1# Copyright (c) 2014 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 5"""A utility to program Chrome OS devices' firmware using servo. 6 7This utility expects the DUT to be connected to a servo device. This allows us 8to put the DUT into the required state and to actually program the DUT's 9firmware using FTDI, USB and/or serial interfaces provided by servo. 10 11Servo state is preserved across the programming process. 12""" 13 14import glob 15import logging 16import os 17import re 18import site 19import time 20import xml.etree.ElementTree 21 22from autotest_lib.client.common_lib import error 23from autotest_lib.server.cros.faft.config.config import Config as FAFTConfig 24 25 26# Number of seconds for program EC/BIOS to time out. 27FIRMWARE_PROGRAM_TIMEOUT_SEC = 600 28 29class ProgrammerError(Exception): 30 """Local exception class wrapper.""" 31 pass 32 33 34class _BaseProgrammer(object): 35 """Class implementing base programmer services. 36 37 Private attributes: 38 _servo: a servo object controlling the servo device 39 _servo_prog_state: a tuple of strings of "<control>:<value>" pairs, 40 listing servo controls and their required values for 41 programming 42 _servo_prog_state_delay: time in second to wait after changing servo 43 controls for programming. 44 _servo_saved_state: a list of the same elements as _servo_prog_state, 45 those which need to be restored after programming 46 _program_cmd: a string, the shell command to run on the servo host 47 to actually program the firmware. Dependent on 48 firmware/hardware type, set by subclasses. 49 """ 50 51 def __init__(self, servo, req_list): 52 """Base constructor. 53 @param servo: a servo object controlling the servo device 54 @param req_list: a list of strings, names of the utilities required 55 to be in the path for the programmer to succeed 56 """ 57 self._servo = servo 58 self._servo_prog_state = () 59 self._servo_prog_state_delay = 0 60 self._servo_saved_state = [] 61 self._program_cmd = '' 62 try: 63 self._servo.system('which %s' % ' '.join(req_list)) 64 except error.AutoservRunError: 65 # TODO: We turn this exception into a warn since the fw programmer 66 # is not working right now, and some systems do not package the 67 # required utilities its checking for. 68 # We should reinstate this exception once the programmer is working 69 # to indicate the missing utilities earlier in the test cycle. 70 # Bug chromium:371011 filed to track this. 71 logging.warn("Ignoring exception when verify required bins : %s", 72 ' '.join(req_list)) 73 74 75 def _set_servo_state(self): 76 """Set servo for programming, while saving the current state.""" 77 logging.debug("Setting servo state for programming") 78 for item in self._servo_prog_state: 79 key, value = item.split(':') 80 try: 81 present = self._servo.get(key) 82 except error.TestFail: 83 logging.warn('Missing servo control: %s', key) 84 continue 85 if present != value: 86 self._servo_saved_state.append('%s:%s' % (key, present)) 87 self._servo.set(key, value) 88 time.sleep(self._servo_prog_state_delay) 89 90 91 def _restore_servo_state(self): 92 """Restore previously saved servo state.""" 93 logging.debug("Restoring servo state after programming") 94 self._servo_saved_state.reverse() # Do it in the reverse order. 95 for item in self._servo_saved_state: 96 key, value = item.split(':') 97 self._servo.set(key, value) 98 99 100 def program(self): 101 """Program the firmware as configured by a subclass.""" 102 self._set_servo_state() 103 try: 104 logging.debug("Programmer command: %s", self._program_cmd) 105 self._servo.system(self._program_cmd, 106 timeout=FIRMWARE_PROGRAM_TIMEOUT_SEC) 107 finally: 108 self._restore_servo_state() 109 110 111class FlashromProgrammer(_BaseProgrammer): 112 """Class for programming AP flashrom.""" 113 114 def __init__(self, servo, keep_ro=False): 115 """Configure required servo state. 116 117 @param servo: a servo object controlling the servo device 118 @param keep_ro: True to keep the RO portion unchanged 119 """ 120 super(FlashromProgrammer, self).__init__(servo, ['flashrom',]) 121 self._keep_ro = keep_ro 122 self._fw_path = None 123 self._tmp_path = '/tmp' 124 self._fw_main = os.path.join(self._tmp_path, 'fw_main') 125 self._wp_ro = os.path.join(self._tmp_path, 'wp_ro') 126 self._ro_vpd = os.path.join(self._tmp_path, 'ro_vpd') 127 self._rw_vpd = os.path.join(self._tmp_path, 'rw_vpd') 128 self._gbb = os.path.join(self._tmp_path, 'gbb') 129 130 131 def program(self): 132 """Program the firmware but preserve VPD and HWID.""" 133 assert self._fw_path is not None 134 self._set_servo_state() 135 try: 136 wp_ro_section = [('WP_RO', self._wp_ro)] 137 rw_vpd_section = [('RW_VPD', self._rw_vpd)] 138 ro_vpd_section = [('RO_VPD', self._ro_vpd)] 139 gbb_section = [('GBB', self._gbb)] 140 if self._keep_ro: 141 # Keep the whole RO portion 142 preserved_sections = wp_ro_section + rw_vpd_section 143 else: 144 preserved_sections = ro_vpd_section + rw_vpd_section 145 146 servo_version = self._servo.get_servo_version() 147 servo_v2_programmer = 'ft2232_spi:type=servo-v2' 148 servo_v3_programmer = 'linux_spi' 149 if servo_version == 'servo_v2': 150 programmer = servo_v2_programmer 151 if self._servo.servo_serial: 152 programmer += ',serial=%s' % self._servo.servo_serial 153 elif servo_version == 'servo_v3': 154 programmer = servo_v3_programmer 155 else: 156 raise Exception('Servo version %s is not supported.' % 157 servo_version) 158 # Save needed sections from current firmware 159 for section in preserved_sections + gbb_section: 160 self._servo.system(' '.join([ 161 'flashrom', '-V', '-p', programmer, 162 '-r', self._fw_main, '-i', '%s:%s' % section]), 163 timeout=FIRMWARE_PROGRAM_TIMEOUT_SEC) 164 165 # Pack the saved VPD into new firmware 166 self._servo.system('cp %s %s' % (self._fw_path, self._fw_main)) 167 img_size = self._servo.system_output( 168 "stat -c '%%s' %s" % self._fw_main) 169 pack_cmd = ['flashrom', 170 '-p', 'dummy:image=%s,size=%s,emulate=VARIABLE_SIZE' % ( 171 self._fw_main, img_size), 172 '-w', self._fw_main] 173 for section in preserved_sections: 174 pack_cmd.extend(['-i', '%s:%s' % section]) 175 self._servo.system(' '.join(pack_cmd), 176 timeout=FIRMWARE_PROGRAM_TIMEOUT_SEC) 177 178 # HWID is inside the RO portion. Don't preserve HWID if we keep RO. 179 if not self._keep_ro: 180 # Read original HWID. The output format is: 181 # hardware_id: RAMBI TEST A_A 0128 182 gbb_hwid_output = self._servo.system_output( 183 'gbb_utility -g --hwid %s' % self._gbb) 184 original_hwid = gbb_hwid_output.split(':', 1)[1].strip() 185 186 # Write HWID to new firmware 187 self._servo.system("gbb_utility -s --hwid='%s' %s" % 188 (original_hwid, self._fw_main)) 189 190 # Flash the new firmware 191 self._servo.system(' '.join([ 192 'flashrom', '-V', '-p', programmer, 193 '-w', self._fw_main]), timeout=FIRMWARE_PROGRAM_TIMEOUT_SEC) 194 finally: 195 self._restore_servo_state() 196 197 198 def prepare_programmer(self, path): 199 """Prepare programmer for programming. 200 201 @param path: a string, name of the file containing the firmware image. 202 @param board: a string, used to find servo voltage setting. 203 """ 204 faft_config = FAFTConfig(self._servo.get_board()) 205 self._fw_path = path 206 self._servo_prog_state_delay = faft_config.servo_prog_state_delay 207 self._servo_prog_state = ( 208 'spi2_vref:%s' % faft_config.spi_voltage, 209 'spi2_buf_en:on', 210 'spi2_buf_on_flex_en:on', 211 'spi_hold:off', 212 'cold_reset:on', 213 'usbpd_reset:on', 214 ) 215 216 217class FlashECProgrammer(_BaseProgrammer): 218 """Class for programming AP flashrom.""" 219 220 def __init__(self, servo): 221 """Configure required servo state.""" 222 super(FlashECProgrammer, self).__init__(servo, ['flash_ec',]) 223 self._servo = servo 224 225 226 def prepare_programmer(self, image): 227 """Prepare programmer for programming. 228 229 @param image: string with the location of the image file 230 """ 231 # TODO: need to not have port be hardcoded 232 self._program_cmd = ('flash_ec --chip=%s --image=%s --port=%s' % 233 (self._servo.get('ec_chip'), image, '9999')) 234 235 236class ProgrammerV2(object): 237 """Main programmer class which provides programmer for BIOS and EC with 238 servo V2.""" 239 240 def __init__(self, servo): 241 self._servo = servo 242 self._valid_boards = self._get_valid_v2_boards() 243 self._bios_programmer = self._factory_bios(self._servo) 244 self._ec_programmer = self._factory_ec(self._servo) 245 246 247 @staticmethod 248 def _get_valid_v2_boards(): 249 """Greps servod config files to look for valid v2 boards. 250 251 @return A list of valid board names. 252 """ 253 site_packages_paths = site.getsitepackages() 254 SERVOD_CONFIG_DATA_DIR = None 255 for p in site_packages_paths: 256 servo_data_path = os.path.join(p, 'servo', 'data') 257 if os.path.exists(servo_data_path): 258 SERVOD_CONFIG_DATA_DIR = servo_data_path 259 break 260 if not SERVOD_CONFIG_DATA_DIR: 261 raise ProgrammerError( 262 'Unable to locate data directory of Python servo module') 263 SERVOFLEX_V2_R0_P50_CONFIG = 'servoflex_v2_r0_p50.xml' 264 SERVO_CONFIG_GLOB = 'servo_*_overlay.xml' 265 SERVO_CONFIG_REGEXP = 'servo_(?P<board>.+)_overlay.xml' 266 267 def is_v2_compatible_board(board_config_path): 268 """Check if the given board config file is v2-compatible. 269 270 @param board_config_path: Path to a board config XML file. 271 272 @return True if the board is v2-compatible; False otherwise. 273 """ 274 configs = [] 275 def get_all_includes(config_path): 276 """Get all included XML config names in the given config file. 277 278 @param config_path: Path to a servo config file. 279 """ 280 root = xml.etree.ElementTree.parse(config_path).getroot() 281 for element in root.findall('include'): 282 include_name = element.find('name').text 283 configs.append(include_name) 284 get_all_includes(os.path.join( 285 SERVOD_CONFIG_DATA_DIR, include_name)) 286 287 get_all_includes(board_config_path) 288 return True if SERVOFLEX_V2_R0_P50_CONFIG in configs else False 289 290 result = [] 291 board_overlays = glob.glob( 292 os.path.join(SERVOD_CONFIG_DATA_DIR, SERVO_CONFIG_GLOB)) 293 for overlay_path in board_overlays: 294 if is_v2_compatible_board(overlay_path): 295 result.append(re.search(SERVO_CONFIG_REGEXP, 296 overlay_path).group('board')) 297 return result 298 299 300 def _factory_bios(self, servo): 301 """Instantiates and returns (bios, ec) programmers for the board. 302 303 @param servo: A servo object. 304 305 @return A programmer for ec. If the programmer is not supported 306 for the board, None will be returned. 307 """ 308 _bios_prog = None 309 _board = servo.get_board() 310 311 servo_prog_state = [ 312 'spi2_buf_en:on', 313 'spi2_buf_on_flex_en:on', 314 'spi_hold:off', 315 'cold_reset:on', 316 ] 317 318 logging.debug('Setting up BIOS programmer for board: %s', _board) 319 if _board in self._valid_boards: 320 _bios_prog = FlashromProgrammer(servo) 321 else: 322 logging.warning('No BIOS programmer found for board: %s', _board) 323 324 return _bios_prog 325 326 327 def _factory_ec(self, servo): 328 """Instantiates and returns ec programmer for the board. 329 330 @param servo: A servo object. 331 332 @return A programmer for ec. If the programmer is not supported 333 for the board, None will be returned. 334 """ 335 _ec_prog = None 336 _board = servo.get_board() 337 338 logging.debug('Setting up EC programmer for board: %s', _board) 339 if _board in self._valid_boards: 340 _ec_prog = FlashECProgrammer(servo) 341 else: 342 logging.warning('No EC programmer found for board: %s', _board) 343 344 return _ec_prog 345 346 347 def program_bios(self, image): 348 """Programs the DUT with provide bios image. 349 350 @param image: (required) location of bios image file. 351 352 """ 353 self._bios_programmer.prepare_programmer(image) 354 self._bios_programmer.program() 355 356 357 def program_ec(self, image): 358 """Programs the DUT with provide ec image. 359 360 @param image: (required) location of ec image file. 361 362 """ 363 self._ec_programmer.prepare_programmer(image) 364 self._ec_programmer.program() 365 366 367class ProgrammerV3(object): 368 """Main programmer class which provides programmer for BIOS and EC with 369 servo V3. 370 371 Different from programmer for servo v2, programmer for servo v3 does not 372 try to validate if the board can use servo V3 to update firmware. As long as 373 the servod process running in beagblebone with given board, the program will 374 attempt to flash bios and ec. 375 376 """ 377 378 def __init__(self, servo): 379 self._servo = servo 380 self._bios_programmer = FlashromProgrammer(servo) 381 self._ec_programmer = FlashECProgrammer(servo) 382 383 384 def program_bios(self, image): 385 """Programs the DUT with provide bios image. 386 387 @param image: (required) location of bios image file. 388 389 """ 390 self._bios_programmer.prepare_programmer(image) 391 self._bios_programmer.program() 392 393 394 def program_ec(self, image): 395 """Programs the DUT with provide ec image. 396 397 @param image: (required) location of ec image file. 398 399 """ 400 self._ec_programmer.prepare_programmer(image) 401 self._ec_programmer.program() 402 403 404class ProgrammerV3RwOnly(object): 405 """Main programmer class which provides programmer for only updating the RW 406 portion of BIOS with servo V3. 407 408 It does nothing on EC, as EC software sync on the next boot will 409 automatically overwrite the EC RW portion, using the EC RW image inside 410 the BIOS RW image. 411 412 """ 413 414 def __init__(self, servo): 415 self._servo = servo 416 self._bios_programmer = FlashromProgrammer(servo, keep_ro=True) 417 418 419 def program_bios(self, image): 420 """Programs the DUT with provide bios image. 421 422 @param image: (required) location of bios image file. 423 424 """ 425 self._bios_programmer.prepare_programmer(image) 426 self._bios_programmer.program() 427 428 429 def program_ec(self, image): 430 """Programs the DUT with provide ec image. 431 432 @param image: (required) location of ec image file. 433 434 """ 435 # Do nothing. EC software sync will update the EC RW. 436 pass 437