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