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 self._servo_version = self._servo.get_servo_version() 130 self._servo_serials = self._servo._server.get_servo_serials() 131 132 133 def program(self): 134 """Program the firmware but preserve VPD and HWID.""" 135 assert self._fw_path is not None 136 self._set_servo_state() 137 try: 138 wp_ro_section = [('WP_RO', self._wp_ro)] 139 rw_vpd_section = [('RW_VPD', self._rw_vpd)] 140 ro_vpd_section = [('RO_VPD', self._ro_vpd)] 141 gbb_section = [('GBB', self._gbb)] 142 if self._keep_ro: 143 # Keep the whole RO portion 144 preserved_sections = wp_ro_section + rw_vpd_section 145 else: 146 preserved_sections = ro_vpd_section + rw_vpd_section 147 148 servo_v2_programmer = 'ft2232_spi:type=servo-v2' 149 servo_v3_programmer = 'linux_spi' 150 servo_v4_with_micro_programmer = 'raiden_spi' 151 servo_v4_with_ccd_programmer = 'raiden_debug_spi:target=AP' 152 if self._servo_version == 'servo_v2': 153 programmer = servo_v2_programmer 154 servo_serial = self._servo_serials.get('main') 155 if servo_serial: 156 programmer += ',serial=%s' % servo_serial 157 elif self._servo_version == 'servo_v3': 158 programmer = servo_v3_programmer 159 elif self._servo_version == 'servo_v4_with_servo_micro': 160 # When a uServo is connected to a DUT with CCD support, the 161 # firmware programmer will always use the uServo to program. 162 servo_micro_serial = self._servo_serials.get('servo_micro') 163 programmer = servo_v4_with_micro_programmer 164 programmer += ',serial=%s' % servo_micro_serial 165 elif self._servo_version == 'servo_v4_with_ccd_cr50': 166 ccd_serial = self._servo_serials.get('ccd') 167 programmer = servo_v4_with_ccd_programmer 168 programmer += ',serial=%s' % ccd_serial 169 else: 170 raise Exception('Servo version %s is not supported.' % 171 self._servo_version) 172 # Save needed sections from current firmware 173 for section in preserved_sections + gbb_section: 174 self._servo.system(' '.join([ 175 'flashrom', '-V', '-p', programmer, 176 '-r', self._fw_main, '-i', '%s:%s' % section]), 177 timeout=FIRMWARE_PROGRAM_TIMEOUT_SEC) 178 179 # Pack the saved VPD into new firmware 180 self._servo.system('cp %s %s' % (self._fw_path, self._fw_main)) 181 img_size = self._servo.system_output( 182 "stat -c '%%s' %s" % self._fw_main) 183 pack_cmd = ['flashrom', 184 '-p', 'dummy:image=%s,size=%s,emulate=VARIABLE_SIZE' % ( 185 self._fw_main, img_size), 186 '-w', self._fw_main] 187 for section in preserved_sections: 188 pack_cmd.extend(['-i', '%s:%s' % section]) 189 self._servo.system(' '.join(pack_cmd), 190 timeout=FIRMWARE_PROGRAM_TIMEOUT_SEC) 191 192 # HWID is inside the RO portion. Don't preserve HWID if we keep RO. 193 if not self._keep_ro: 194 # Read original HWID. The output format is: 195 # hardware_id: RAMBI TEST A_A 0128 196 gbb_hwid_output = self._servo.system_output( 197 'gbb_utility -g --hwid %s' % self._gbb) 198 original_hwid = gbb_hwid_output.split(':', 1)[1].strip() 199 200 # Write HWID to new firmware 201 self._servo.system("gbb_utility -s --hwid='%s' %s" % 202 (original_hwid, self._fw_main)) 203 204 # Flash the new firmware 205 self._servo.system(' '.join([ 206 'flashrom', '-V', '-p', programmer, 207 '-w', self._fw_main]), timeout=FIRMWARE_PROGRAM_TIMEOUT_SEC) 208 finally: 209 self._servo.get_power_state_controller().reset() 210 self._restore_servo_state() 211 212 213 def prepare_programmer(self, path): 214 """Prepare programmer for programming. 215 216 @param path: a string, name of the file containing the firmware image. 217 """ 218 self._fw_path = path 219 # CCD takes care holding AP/EC. Don't need the following steps. 220 if self._servo_version != 'servo_v4_with_ccd_cr50': 221 faft_config = FAFTConfig(self._servo.get_board()) 222 self._servo_prog_state_delay = faft_config.servo_prog_state_delay 223 self._servo_prog_state = ( 224 'spi2_vref:%s' % faft_config.spi_voltage, 225 'spi2_buf_en:on', 226 'spi2_buf_on_flex_en:on', 227 'spi_hold:off', 228 'cold_reset:on', 229 'usbpd_reset:on', 230 ) 231 232 233class FlashECProgrammer(_BaseProgrammer): 234 """Class for programming AP flashrom.""" 235 236 def __init__(self, servo): 237 """Configure required servo state.""" 238 super(FlashECProgrammer, self).__init__(servo, ['flash_ec',]) 239 self._servo = servo 240 self._servo_version = self._servo.get_servo_version() 241 242 def prepare_programmer(self, image): 243 """Prepare programmer for programming. 244 245 @param image: string with the location of the image file 246 """ 247 port = self._servo._servo_host.servo_port 248 self._program_cmd = ('flash_ec --chip=%s --image=%s --port=%d' % 249 (self._servo.get('ec_chip'), image, port)) 250 if self._servo_version == 'servo_v4_with_ccd_cr50': 251 self._program_cmd += ' --raiden' 252 253 254class ProgrammerV2(object): 255 """Main programmer class which provides programmer for BIOS and EC with 256 servo V2.""" 257 258 def __init__(self, servo): 259 self._servo = servo 260 self._valid_boards = self._get_valid_v2_boards() 261 self._bios_programmer = self._factory_bios(self._servo) 262 self._ec_programmer = self._factory_ec(self._servo) 263 264 265 @staticmethod 266 def _get_valid_v2_boards(): 267 """Greps servod config files to look for valid v2 boards. 268 269 @return A list of valid board names. 270 """ 271 site_packages_paths = site.getsitepackages() 272 SERVOD_CONFIG_DATA_DIR = None 273 for p in site_packages_paths: 274 servo_data_path = os.path.join(p, 'servo', 'data') 275 if os.path.exists(servo_data_path): 276 SERVOD_CONFIG_DATA_DIR = servo_data_path 277 break 278 if not SERVOD_CONFIG_DATA_DIR: 279 raise ProgrammerError( 280 'Unable to locate data directory of Python servo module') 281 SERVOFLEX_V2_R0_P50_CONFIG = 'servoflex_v2_r0_p50.xml' 282 SERVO_CONFIG_GLOB = 'servo_*_overlay.xml' 283 SERVO_CONFIG_REGEXP = 'servo_(?P<board>.+)_overlay.xml' 284 285 def is_v2_compatible_board(board_config_path): 286 """Check if the given board config file is v2-compatible. 287 288 @param board_config_path: Path to a board config XML file. 289 290 @return True if the board is v2-compatible; False otherwise. 291 """ 292 configs = [] 293 def get_all_includes(config_path): 294 """Get all included XML config names in the given config file. 295 296 @param config_path: Path to a servo config file. 297 """ 298 root = xml.etree.ElementTree.parse(config_path).getroot() 299 for element in root.findall('include'): 300 include_name = element.find('name').text 301 configs.append(include_name) 302 get_all_includes(os.path.join( 303 SERVOD_CONFIG_DATA_DIR, include_name)) 304 305 get_all_includes(board_config_path) 306 return True if SERVOFLEX_V2_R0_P50_CONFIG in configs else False 307 308 result = [] 309 board_overlays = glob.glob( 310 os.path.join(SERVOD_CONFIG_DATA_DIR, SERVO_CONFIG_GLOB)) 311 for overlay_path in board_overlays: 312 if is_v2_compatible_board(overlay_path): 313 result.append(re.search(SERVO_CONFIG_REGEXP, 314 overlay_path).group('board')) 315 return result 316 317 318 def _get_flashrom_programmer(self, servo): 319 """Gets a proper flashrom programmer. 320 321 @param servo: A servo object. 322 323 @return A programmer for flashrom. 324 """ 325 return FlashromProgrammer(servo) 326 327 328 def _factory_bios(self, servo): 329 """Instantiates and returns (bios, ec) programmers for the board. 330 331 @param servo: A servo object. 332 333 @return A programmer for ec. If the programmer is not supported 334 for the board, None will be returned. 335 """ 336 _bios_prog = None 337 _board = servo.get_board() 338 339 logging.debug('Setting up BIOS programmer for board: %s', _board) 340 if _board in self._valid_boards: 341 _bios_prog = self._get_flashrom_programmer(servo) 342 else: 343 logging.warning('No BIOS programmer found for board: %s', _board) 344 345 return _bios_prog 346 347 348 def _factory_ec(self, servo): 349 """Instantiates and returns ec programmer for the board. 350 351 @param servo: A servo object. 352 353 @return A programmer for ec. If the programmer is not supported 354 for the board, None will be returned. 355 """ 356 _ec_prog = None 357 _board = servo.get_board() 358 359 logging.debug('Setting up EC programmer for board: %s', _board) 360 if _board in self._valid_boards: 361 _ec_prog = FlashECProgrammer(servo) 362 else: 363 logging.warning('No EC programmer found for board: %s', _board) 364 365 return _ec_prog 366 367 368 def program_bios(self, image): 369 """Programs the DUT with provide bios image. 370 371 @param image: (required) location of bios image file. 372 373 """ 374 self._bios_programmer.prepare_programmer(image) 375 self._bios_programmer.program() 376 377 378 def program_ec(self, image): 379 """Programs the DUT with provide ec image. 380 381 @param image: (required) location of ec image file. 382 383 """ 384 self._ec_programmer.prepare_programmer(image) 385 self._ec_programmer.program() 386 387 388class ProgrammerV2RwOnly(ProgrammerV2): 389 """Main programmer class which provides programmer for only updating the RW 390 portion of BIOS with servo V2. 391 392 It does nothing on EC, as EC software sync on the next boot will 393 automatically overwrite the EC RW portion, using the EC RW image inside 394 the BIOS RW image. 395 396 """ 397 398 def _get_flashrom_programmer(self, servo): 399 """Gets a proper flashrom programmer. 400 401 @param servo: A servo object. 402 403 @return A programmer for flashrom. 404 """ 405 return FlashromProgrammer(servo, keep_ro=True) 406 407 408 def program_ec(self, image): 409 """Programs the DUT with provide ec image. 410 411 @param image: (required) location of ec image file. 412 413 """ 414 # Do nothing. EC software sync will update the EC RW. 415 pass 416 417 418class ProgrammerV3(object): 419 """Main programmer class which provides programmer for BIOS and EC with 420 servo V3. 421 422 Different from programmer for servo v2, programmer for servo v3 does not 423 try to validate if the board can use servo V3 to update firmware. As long as 424 the servod process running in beagblebone with given board, the program will 425 attempt to flash bios and ec. 426 427 """ 428 429 def __init__(self, servo): 430 self._servo = servo 431 self._bios_programmer = FlashromProgrammer(servo) 432 self._ec_programmer = FlashECProgrammer(servo) 433 434 435 def program_bios(self, image): 436 """Programs the DUT with provide bios image. 437 438 @param image: (required) location of bios image file. 439 440 """ 441 self._bios_programmer.prepare_programmer(image) 442 self._bios_programmer.program() 443 444 445 def program_ec(self, image): 446 """Programs the DUT with provide ec image. 447 448 @param image: (required) location of ec image file. 449 450 """ 451 self._ec_programmer.prepare_programmer(image) 452 self._ec_programmer.program() 453 454 455class ProgrammerV3RwOnly(ProgrammerV3): 456 """Main programmer class which provides programmer for only updating the RW 457 portion of BIOS with servo V3. 458 459 It does nothing on EC, as EC software sync on the next boot will 460 automatically overwrite the EC RW portion, using the EC RW image inside 461 the BIOS RW image. 462 463 """ 464 465 def __init__(self, servo): 466 self._servo = servo 467 self._bios_programmer = FlashromProgrammer(servo, keep_ro=True) 468 469 470 def program_ec(self, image): 471 """Programs the DUT with provide ec image. 472 473 @param image: (required) location of ec image file. 474 475 """ 476 # Do nothing. EC software sync will update the EC RW. 477 pass 478