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 = 900 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_debug_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._ec_chip == 'stm32': 274 self._program_cmd += ' --bitbang_rate=57600' 275 self._program_cmd += ' --verify' 276 self._program_cmd += ' --verbose' 277 278 279class ProgrammerV2(object): 280 """Main programmer class which provides programmer for BIOS and EC with 281 servo V2.""" 282 283 def __init__(self, servo): 284 self._servo = servo 285 self._valid_boards = self._get_valid_v2_boards() 286 self._bios_programmer = self._factory_bios(self._servo) 287 self._ec_programmer = self._factory_ec(self._servo) 288 289 290 @staticmethod 291 def _get_valid_v2_boards(): 292 """Greps servod config files to look for valid v2 boards. 293 294 @return A list of valid board names. 295 """ 296 site_packages_paths = site.getsitepackages() 297 SERVOD_CONFIG_DATA_DIR = None 298 for p in site_packages_paths: 299 servo_data_path = os.path.join(p, 'servo', 'data') 300 if os.path.exists(servo_data_path): 301 SERVOD_CONFIG_DATA_DIR = servo_data_path 302 break 303 if not SERVOD_CONFIG_DATA_DIR: 304 raise ProgrammerError( 305 'Unable to locate data directory of Python servo module') 306 SERVOFLEX_V2_R0_P50_CONFIG = 'servoflex_v2_r0_p50.xml' 307 SERVO_CONFIG_GLOB = 'servo_*_overlay.xml' 308 SERVO_CONFIG_REGEXP = 'servo_(?P<board>.+)_overlay.xml' 309 310 def is_v2_compatible_board(board_config_path): 311 """Check if the given board config file is v2-compatible. 312 313 @param board_config_path: Path to a board config XML file. 314 315 @return True if the board is v2-compatible; False otherwise. 316 """ 317 configs = [] 318 def get_all_includes(config_path): 319 """Get all included XML config names in the given config file. 320 321 @param config_path: Path to a servo config file. 322 """ 323 root = xml.etree.ElementTree.parse(config_path).getroot() 324 for element in root.findall('include'): 325 include_name = element.find('name').text 326 configs.append(include_name) 327 get_all_includes(os.path.join( 328 SERVOD_CONFIG_DATA_DIR, include_name)) 329 330 get_all_includes(board_config_path) 331 return True if SERVOFLEX_V2_R0_P50_CONFIG in configs else False 332 333 result = [] 334 board_overlays = glob.glob( 335 os.path.join(SERVOD_CONFIG_DATA_DIR, SERVO_CONFIG_GLOB)) 336 for overlay_path in board_overlays: 337 if is_v2_compatible_board(overlay_path): 338 result.append(re.search(SERVO_CONFIG_REGEXP, 339 overlay_path).group('board')) 340 return result 341 342 343 def _get_flashrom_programmer(self, servo): 344 """Gets a proper flashrom programmer. 345 346 @param servo: A servo object. 347 348 @return A programmer for flashrom. 349 """ 350 return FlashromProgrammer(servo) 351 352 353 def _factory_bios(self, servo): 354 """Instantiates and returns (bios, ec) programmers for the board. 355 356 @param servo: A servo object. 357 358 @return A programmer for ec. If the programmer is not supported 359 for the board, None will be returned. 360 """ 361 _bios_prog = None 362 _board = servo.get_board() 363 364 logging.debug('Setting up BIOS programmer for board: %s', _board) 365 if _board in self._valid_boards: 366 _bios_prog = self._get_flashrom_programmer(servo) 367 else: 368 logging.warning('No BIOS programmer found for board: %s', _board) 369 370 return _bios_prog 371 372 373 def _factory_ec(self, servo): 374 """Instantiates and returns ec programmer for the board. 375 376 @param servo: A servo object. 377 378 @return A programmer for ec. If the programmer is not supported 379 for the board, None will be returned. 380 """ 381 _ec_prog = None 382 _board = servo.get_board() 383 384 logging.debug('Setting up EC programmer for board: %s', _board) 385 if _board in self._valid_boards: 386 _ec_prog = FlashECProgrammer(servo) 387 else: 388 logging.warning('No EC programmer found for board: %s', _board) 389 390 return _ec_prog 391 392 393 def program_bios(self, image): 394 """Programs the DUT with provide bios image. 395 396 @param image: (required) location of bios image file. 397 398 """ 399 self._bios_programmer.prepare_programmer(image) 400 self._bios_programmer.program() 401 402 403 def program_ec(self, image): 404 """Programs the DUT with provide ec image. 405 406 @param image: (required) location of ec image file. 407 408 """ 409 self._ec_programmer.prepare_programmer(image) 410 self._ec_programmer.program() 411 412 413class ProgrammerV2RwOnly(ProgrammerV2): 414 """Main programmer class which provides programmer for only updating the RW 415 portion of BIOS with servo V2. 416 417 It does nothing on EC, as EC software sync on the next boot will 418 automatically overwrite the EC RW portion, using the EC RW image inside 419 the BIOS RW image. 420 421 """ 422 423 def _get_flashrom_programmer(self, servo): 424 """Gets a proper flashrom programmer. 425 426 @param servo: A servo object. 427 428 @return A programmer for flashrom. 429 """ 430 return FlashromProgrammer(servo, keep_ro=True) 431 432 433 def program_ec(self, image): 434 """Programs the DUT with provide ec image. 435 436 @param image: (required) location of ec image file. 437 438 """ 439 # Do nothing. EC software sync will update the EC RW. 440 pass 441 442 443class ProgrammerV3(object): 444 """Main programmer class which provides programmer for BIOS and EC with 445 servo V3. 446 447 Different from programmer for servo v2, programmer for servo v3 does not 448 try to validate if the board can use servo V3 to update firmware. As long as 449 the servod process running in beagblebone with given board, the program will 450 attempt to flash bios and ec. 451 452 """ 453 454 def __init__(self, servo): 455 self._servo = servo 456 self._bios_programmer = FlashromProgrammer(servo) 457 self._ec_programmer = FlashECProgrammer(servo) 458 459 460 def program_bios(self, image): 461 """Programs the DUT with provide bios image. 462 463 @param image: (required) location of bios image file. 464 465 """ 466 self._bios_programmer.prepare_programmer(image) 467 self._bios_programmer.program() 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 self._ec_programmer.prepare_programmer(image) 477 self._ec_programmer.program() 478 479 480class ProgrammerV3RwOnly(ProgrammerV3): 481 """Main programmer class which provides programmer for only updating the RW 482 portion of BIOS with servo V3. 483 484 It does nothing on EC, as EC software sync on the next boot will 485 automatically overwrite the EC RW portion, using the EC RW image inside 486 the BIOS RW image. 487 488 """ 489 490 def __init__(self, servo): 491 self._servo = servo 492 self._bios_programmer = FlashromProgrammer(servo, keep_ro=True) 493 494 495 def program_ec(self, image): 496 """Programs the DUT with provide ec image. 497 498 @param image: (required) location of ec image file. 499 500 """ 501 # Do nothing. EC software sync will update the EC RW. 502 pass 503