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