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_host: a host object running commands like 'flashrom' 40 _servo_prog_state: a tuple of strings of "<control>:<value>" pairs, 41 listing servo controls and their required values for 42 programming 43 _servo_prog_state_delay: time in second to wait after changing servo 44 controls for programming. 45 _servo_saved_state: a list of the same elements as _servo_prog_state, 46 those which need to be restored after programming 47 _program_cmd: a string, the shell command to run on the servo host 48 to actually program the firmware. Dependent on 49 firmware/hardware type, set by subclasses. 50 """ 51 52 def __init__(self, servo, req_list, servo_host=None): 53 """Base constructor. 54 @param servo: a servo object controlling the servo device 55 @param req_list: a list of strings, names of the utilities required 56 to be in the path for the programmer to succeed 57 @param servo_host: a host object to execute commands. Default to None, 58 using the host object from the above servo object 59 """ 60 self._servo = servo 61 self._servo_prog_state = () 62 self._servo_prog_state_delay = 0 63 self._servo_saved_state = [] 64 self._program_cmd = '' 65 self._servo_host = servo_host 66 if self._servo_host is None: 67 self._servo_host = self._servo._servo_host 68 69 try: 70 self._servo_host.run('which %s' % ' '.join(req_list)) 71 except error.AutoservRunError: 72 # TODO: We turn this exception into a warn since the fw programmer 73 # is not working right now, and some systems do not package the 74 # required utilities its checking for. 75 # We should reinstate this exception once the programmer is working 76 # to indicate the missing utilities earlier in the test cycle. 77 # Bug chromium:371011 filed to track this. 78 logging.warn("Ignoring exception when verify required bins : %s", 79 ' '.join(req_list)) 80 81 82 def _set_servo_state(self): 83 """Set servo for programming, while saving the current state.""" 84 logging.debug("Setting servo state for programming") 85 for item in self._servo_prog_state: 86 key, value = item.split(':') 87 try: 88 present = self._servo.get(key) 89 except error.TestFail: 90 logging.warn('Missing servo control: %s', key) 91 continue 92 if present != value: 93 self._servo_saved_state.append('%s:%s' % (key, present)) 94 self._servo.set(key, value) 95 time.sleep(self._servo_prog_state_delay) 96 97 98 def _restore_servo_state(self): 99 """Restore previously saved servo state.""" 100 logging.debug("Restoring servo state after programming") 101 self._servo_saved_state.reverse() # Do it in the reverse order. 102 for item in self._servo_saved_state: 103 key, value = item.split(':') 104 self._servo.set(key, value) 105 106 107 def program(self): 108 """Program the firmware as configured by a subclass.""" 109 self._set_servo_state() 110 try: 111 logging.debug("Programmer command: %s", self._program_cmd) 112 self._servo_host.run(self._program_cmd, 113 timeout=FIRMWARE_PROGRAM_TIMEOUT_SEC) 114 finally: 115 self._restore_servo_state() 116 117 118class FlashromProgrammer(_BaseProgrammer): 119 """Class for programming AP flashrom.""" 120 121 def __init__(self, servo, keep_ro=False): 122 """Configure required servo state. 123 124 @param servo: a servo object controlling the servo device 125 @param keep_ro: True to keep the RO portion unchanged 126 """ 127 super(FlashromProgrammer, self).__init__(servo, ['flashrom',]) 128 self._keep_ro = keep_ro 129 self._fw_path = None 130 self._tmp_path = '/tmp' 131 self._fw_main = os.path.join(self._tmp_path, 'fw_main') 132 self._wp_ro = os.path.join(self._tmp_path, 'wp_ro') 133 self._ro_vpd = os.path.join(self._tmp_path, 'ro_vpd') 134 self._rw_vpd = os.path.join(self._tmp_path, 'rw_vpd') 135 self._gbb = os.path.join(self._tmp_path, 'gbb') 136 self._servo_version = self._servo.get_servo_version() 137 self._servo_serials = self._servo._server.get_servo_serials() 138 139 140 def program(self): 141 """Program the firmware but preserve VPD and HWID.""" 142 assert self._fw_path is not None 143 self._set_servo_state() 144 try: 145 wp_ro_section = [('WP_RO', self._wp_ro)] 146 rw_vpd_section = [('RW_VPD', self._rw_vpd)] 147 ro_vpd_section = [('RO_VPD', self._ro_vpd)] 148 gbb_section = [('GBB', self._gbb)] 149 if self._keep_ro: 150 # Keep the whole RO portion 151 preserved_sections = wp_ro_section + rw_vpd_section 152 else: 153 preserved_sections = ro_vpd_section + rw_vpd_section 154 155 servo_v2_programmer = 'ft2232_spi:type=servo-v2' 156 servo_v3_programmer = 'linux_spi' 157 servo_v4_with_micro_programmer = 'raiden_spi' 158 servo_v4_with_ccd_programmer = 'raiden_debug_spi:target=AP' 159 if self._servo_version == 'servo_v2': 160 programmer = servo_v2_programmer 161 servo_serial = self._servo_serials.get('main') 162 if servo_serial: 163 programmer += ',serial=%s' % servo_serial 164 elif self._servo_version == 'servo_v3': 165 programmer = servo_v3_programmer 166 elif self._servo_version == 'servo_v4_with_servo_micro': 167 # When a uServo is connected to a DUT with CCD support, the 168 # firmware programmer will always use the uServo to program. 169 servo_micro_serial = self._servo_serials.get('servo_micro') 170 programmer = servo_v4_with_micro_programmer 171 programmer += ',serial=%s' % servo_micro_serial 172 elif self._servo_version == 'servo_v4_with_ccd_cr50': 173 ccd_serial = self._servo_serials.get('ccd') 174 programmer = servo_v4_with_ccd_programmer 175 programmer += ',serial=%s' % ccd_serial 176 else: 177 raise Exception('Servo version %s is not supported.' % 178 self._servo_version) 179 # Save needed sections from current firmware 180 for section in preserved_sections + gbb_section: 181 self._servo_host.run(' '.join([ 182 'flashrom', '-V', '-p', programmer, 183 '-r', self._fw_main, '-i', '%s:%s' % section]), 184 timeout=FIRMWARE_PROGRAM_TIMEOUT_SEC) 185 186 # Pack the saved VPD into new firmware 187 self._servo_host.run('cp %s %s' % (self._fw_path, self._fw_main)) 188 img_size = self._servo_host.run_output( 189 "stat -c '%%s' %s" % self._fw_main) 190 pack_cmd = ['flashrom', 191 '-p', 'dummy:image=%s,size=%s,emulate=VARIABLE_SIZE' % ( 192 self._fw_main, img_size), 193 '-w', self._fw_main] 194 for section in preserved_sections: 195 pack_cmd.extend(['-i', '%s:%s' % section]) 196 self._servo_host.run(' '.join(pack_cmd), 197 timeout=FIRMWARE_PROGRAM_TIMEOUT_SEC) 198 199 # HWID is inside the RO portion. Don't preserve HWID if we keep RO. 200 if not self._keep_ro: 201 # Read original HWID. The output format is: 202 # hardware_id: RAMBI TEST A_A 0128 203 gbb_hwid_output = self._servo_host.run_output( 204 'gbb_utility -g --hwid %s' % self._gbb) 205 original_hwid = gbb_hwid_output.split(':', 1)[1].strip() 206 207 # Write HWID to new firmware 208 self._servo_host.run("gbb_utility -s --hwid='%s' %s" % 209 (original_hwid, self._fw_main)) 210 211 # Flash the new firmware 212 self._servo_host.run(' '.join([ 213 'flashrom', '-V', '-p', programmer, 214 '-w', self._fw_main]), timeout=FIRMWARE_PROGRAM_TIMEOUT_SEC) 215 finally: 216 self._servo.get_power_state_controller().reset() 217 self._restore_servo_state() 218 219 220 def prepare_programmer(self, path): 221 """Prepare programmer for programming. 222 223 @param path: a string, name of the file containing the firmware image. 224 """ 225 self._fw_path = path 226 # CCD takes care holding AP/EC. Don't need the following steps. 227 if self._servo_version != 'servo_v4_with_ccd_cr50': 228 faft_config = FAFTConfig(self._servo.get_board()) 229 self._servo_prog_state_delay = faft_config.servo_prog_state_delay 230 self._servo_prog_state = ( 231 'spi2_vref:%s' % faft_config.spi_voltage, 232 'spi2_buf_en:on', 233 'spi2_buf_on_flex_en:on', 234 'spi_hold:off', 235 'cold_reset:on', 236 'usbpd_reset:on', 237 ) 238 239 240class FlashECProgrammer(_BaseProgrammer): 241 """Class for programming AP flashrom.""" 242 243 def __init__(self, servo, host=None, ec_chip=None): 244 """Configure required servo state. 245 246 @param servo: a servo object controlling the servo device 247 @param host: a host object to execute commands. Default to None, 248 using the host object from the above servo object, i.e. 249 a servo host. A CrOS host object can be passed here 250 such that it executes commands on the CrOS device. 251 @param ec_chip: a string of EC chip. Default to None, using the 252 EC chip name reported by servo, the primary EC. 253 Can pass a different chip name, for the case of 254 the base EC. 255 256 """ 257 super(FlashECProgrammer, self).__init__(servo, ['flash_ec'], host) 258 self._servo_version = self._servo.get_servo_version() 259 if ec_chip is None: 260 self._ec_chip = servo.get('ec_chip') 261 else: 262 self._ec_chip = ec_chip 263 264 def prepare_programmer(self, image): 265 """Prepare programmer for programming. 266 267 @param image: string with the location of the image file 268 """ 269 # Get the port of servod. flash_ec may use it to talk to servod. 270 port = self._servo._servo_host.servo_port 271 self._program_cmd = ('flash_ec --chip=%s --image=%s --port=%d' % 272 (self._ec_chip, image, port)) 273 if self._servo_version == 'servo_v4_with_ccd_cr50': 274 self._program_cmd += ' --raiden' 275 276 277class ProgrammerV2(object): 278 """Main programmer class which provides programmer for BIOS and EC with 279 servo V2.""" 280 281 def __init__(self, servo): 282 self._servo = servo 283 self._valid_boards = self._get_valid_v2_boards() 284 self._bios_programmer = self._factory_bios(self._servo) 285 self._ec_programmer = self._factory_ec(self._servo) 286 287 288 @staticmethod 289 def _get_valid_v2_boards(): 290 """Greps servod config files to look for valid v2 boards. 291 292 @return A list of valid board names. 293 """ 294 site_packages_paths = site.getsitepackages() 295 SERVOD_CONFIG_DATA_DIR = None 296 for p in site_packages_paths: 297 servo_data_path = os.path.join(p, 'servo', 'data') 298 if os.path.exists(servo_data_path): 299 SERVOD_CONFIG_DATA_DIR = servo_data_path 300 break 301 if not SERVOD_CONFIG_DATA_DIR: 302 raise ProgrammerError( 303 'Unable to locate data directory of Python servo module') 304 SERVOFLEX_V2_R0_P50_CONFIG = 'servoflex_v2_r0_p50.xml' 305 SERVO_CONFIG_GLOB = 'servo_*_overlay.xml' 306 SERVO_CONFIG_REGEXP = 'servo_(?P<board>.+)_overlay.xml' 307 308 def is_v2_compatible_board(board_config_path): 309 """Check if the given board config file is v2-compatible. 310 311 @param board_config_path: Path to a board config XML file. 312 313 @return True if the board is v2-compatible; False otherwise. 314 """ 315 configs = [] 316 def get_all_includes(config_path): 317 """Get all included XML config names in the given config file. 318 319 @param config_path: Path to a servo config file. 320 """ 321 root = xml.etree.ElementTree.parse(config_path).getroot() 322 for element in root.findall('include'): 323 include_name = element.find('name').text 324 configs.append(include_name) 325 get_all_includes(os.path.join( 326 SERVOD_CONFIG_DATA_DIR, include_name)) 327 328 get_all_includes(board_config_path) 329 return True if SERVOFLEX_V2_R0_P50_CONFIG in configs else False 330 331 result = [] 332 board_overlays = glob.glob( 333 os.path.join(SERVOD_CONFIG_DATA_DIR, SERVO_CONFIG_GLOB)) 334 for overlay_path in board_overlays: 335 if is_v2_compatible_board(overlay_path): 336 result.append(re.search(SERVO_CONFIG_REGEXP, 337 overlay_path).group('board')) 338 return result 339 340 341 def _get_flashrom_programmer(self, servo): 342 """Gets a proper flashrom programmer. 343 344 @param servo: A servo object. 345 346 @return A programmer for flashrom. 347 """ 348 return FlashromProgrammer(servo) 349 350 351 def _factory_bios(self, servo): 352 """Instantiates and returns (bios, ec) programmers for the board. 353 354 @param servo: A servo object. 355 356 @return A programmer for ec. If the programmer is not supported 357 for the board, None will be returned. 358 """ 359 _bios_prog = None 360 _board = servo.get_board() 361 362 logging.debug('Setting up BIOS programmer for board: %s', _board) 363 if _board in self._valid_boards: 364 _bios_prog = self._get_flashrom_programmer(servo) 365 else: 366 logging.warning('No BIOS programmer found for board: %s', _board) 367 368 return _bios_prog 369 370 371 def _factory_ec(self, servo): 372 """Instantiates and returns ec programmer for the board. 373 374 @param servo: A servo object. 375 376 @return A programmer for ec. If the programmer is not supported 377 for the board, None will be returned. 378 """ 379 _ec_prog = None 380 _board = servo.get_board() 381 382 logging.debug('Setting up EC programmer for board: %s', _board) 383 if _board in self._valid_boards: 384 _ec_prog = FlashECProgrammer(servo) 385 else: 386 logging.warning('No EC programmer found for board: %s', _board) 387 388 return _ec_prog 389 390 391 def program_bios(self, image): 392 """Programs the DUT with provide bios image. 393 394 @param image: (required) location of bios image file. 395 396 """ 397 self._bios_programmer.prepare_programmer(image) 398 self._bios_programmer.program() 399 400 401 def program_ec(self, image): 402 """Programs the DUT with provide ec image. 403 404 @param image: (required) location of ec image file. 405 406 """ 407 self._ec_programmer.prepare_programmer(image) 408 self._ec_programmer.program() 409 410 411class ProgrammerV2RwOnly(ProgrammerV2): 412 """Main programmer class which provides programmer for only updating the RW 413 portion of BIOS with servo V2. 414 415 It does nothing on EC, as EC software sync on the next boot will 416 automatically overwrite the EC RW portion, using the EC RW image inside 417 the BIOS RW image. 418 419 """ 420 421 def _get_flashrom_programmer(self, servo): 422 """Gets a proper flashrom programmer. 423 424 @param servo: A servo object. 425 426 @return A programmer for flashrom. 427 """ 428 return FlashromProgrammer(servo, keep_ro=True) 429 430 431 def program_ec(self, image): 432 """Programs the DUT with provide ec image. 433 434 @param image: (required) location of ec image file. 435 436 """ 437 # Do nothing. EC software sync will update the EC RW. 438 pass 439 440 441class ProgrammerV3(object): 442 """Main programmer class which provides programmer for BIOS and EC with 443 servo V3. 444 445 Different from programmer for servo v2, programmer for servo v3 does not 446 try to validate if the board can use servo V3 to update firmware. As long as 447 the servod process running in beagblebone with given board, the program will 448 attempt to flash bios and ec. 449 450 """ 451 452 def __init__(self, servo): 453 self._servo = servo 454 self._bios_programmer = FlashromProgrammer(servo) 455 self._ec_programmer = FlashECProgrammer(servo) 456 457 458 def program_bios(self, image): 459 """Programs the DUT with provide bios image. 460 461 @param image: (required) location of bios image file. 462 463 """ 464 self._bios_programmer.prepare_programmer(image) 465 self._bios_programmer.program() 466 467 468 def program_ec(self, image): 469 """Programs the DUT with provide ec image. 470 471 @param image: (required) location of ec image file. 472 473 """ 474 self._ec_programmer.prepare_programmer(image) 475 self._ec_programmer.program() 476 477 478class ProgrammerV3RwOnly(ProgrammerV3): 479 """Main programmer class which provides programmer for only updating the RW 480 portion of BIOS with servo V3. 481 482 It does nothing on EC, as EC software sync on the next boot will 483 automatically overwrite the EC RW portion, using the EC RW image inside 484 the BIOS RW image. 485 486 """ 487 488 def __init__(self, servo): 489 self._servo = servo 490 self._bios_programmer = FlashromProgrammer(servo, keep_ro=True) 491 492 493 def program_ec(self, image): 494 """Programs the DUT with provide ec image. 495 496 @param image: (required) location of ec image file. 497 498 """ 499 # Do nothing. EC software sync will update the EC RW. 500 pass 501 502 503class ProgrammerDfu(object): 504 """Main programmer class which provides programmer for Base EC via DFU. 505 506 It programs through the DUT, i.e. running the flash_ec script on DUT 507 instead of running it in beaglebone (a host of the servo board). 508 It is independent of the version of servo board as long as the servo 509 board has the ec_boot_mode interface. 510 511 """ 512 513 def __init__(self, servo, cros_host): 514 self._servo = servo 515 self._cros_host = cros_host 516 # Get the chip name of the base EC and append '_dfu' to it, like 517 # 'stm32' -> 'stm32_dfu'. 518 ec_chip = servo.get(servo.get_base_board() + '_ec_chip') + '_dfu' 519 self._ec_programmer = FlashECProgrammer(servo, cros_host, ec_chip) 520 521 522 def program_ec(self, image): 523 """Programs the DUT with provide ec image. 524 525 @param image: (required) location of ec image file. 526 527 """ 528 self._ec_programmer.prepare_programmer(image) 529 ec_boot_mode = self._servo.get_base_board() + '_ec_boot_mode' 530 try: 531 self._servo.set(ec_boot_mode, 'on') 532 # Power cycle the base to enter DFU mode 533 self._cros_host.run('ectool gpioset PP3300_DX_BASE 0') 534 self._cros_host.run('ectool gpioset PP3300_DX_BASE 1') 535 self._ec_programmer.program() 536 finally: 537 self._servo.set(ec_boot_mode, 'off') 538 # Power cycle the base to back normal mode 539 self._cros_host.run('ectool gpioset PP3300_DX_BASE 0') 540 self._cros_host.run('ectool gpioset PP3300_DX_BASE 1') 541