blob: ef79c913e1d96df01b4fd5751f68c5efbf74f1d8 [file] [log] [blame]
Yusuf Mohsinally6c078ae2013-11-21 11:06:42 -08001# 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
Ricky Liangc101a562014-05-15 10:56:15 +080014import glob
Yusuf Mohsinally6c078ae2013-11-21 11:06:42 -080015import logging
16import os
Ricky Liangc101a562014-05-15 10:56:15 +080017import re
Ricky Liangc7cc3402014-05-24 00:25:26 +080018import site
Tom Wai-Hong Tama8d4a102015-10-10 10:43:20 +080019import time
Ricky Liangf19ab9f2014-06-11 15:50:44 +080020import xml.etree.ElementTree
Yusuf Mohsinally6c078ae2013-11-21 11:06:42 -080021
Yusuf Mohsinally8c4631d2014-05-05 18:20:54 -070022from autotest_lib.client.common_lib import error
Yusuf Mohsinally6c078ae2013-11-21 11:06:42 -080023from autotest_lib.server.cros.faft.config.config import Config as FAFTConfig
24
25
Dan Shifecdaf42015-07-28 10:17:26 -070026# Number of seconds for program EC/BIOS to time out.
27FIRMWARE_PROGRAM_TIMEOUT_SEC = 600
28
Yusuf Mohsinally6c078ae2013-11-21 11:06:42 -080029class 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
Tom Wai-Hong Tama8d4a102015-10-10 10:43:20 +080042 _servo_prog_state_delay: time in second to wait after changing servo
43 controls for programming.
Yusuf Mohsinally6c078ae2013-11-21 11:06:42 -080044 _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 = ()
Tom Wai-Hong Tama8d4a102015-10-10 10:43:20 +080059 self._servo_prog_state_delay = 0
Yusuf Mohsinally6c078ae2013-11-21 11:06:42 -080060 self._servo_saved_state = []
61 self._program_cmd = ''
Yusuf Mohsinally8c4631d2014-05-05 18:20:54 -070062 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))
Yusuf Mohsinally6c078ae2013-11-21 11:06:42 -080073
Dan Shia5fef052015-05-18 23:28:47 -070074
Yusuf Mohsinally6c078ae2013-11-21 11:06:42 -080075 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(':')
Tom Wai-Hong Tama8d4a102015-10-10 10:43:20 +080080 try:
81 present = self._servo.get(key)
82 except error.TestFail:
83 logging.warn('Missing servo control: %s', key)
84 continue
Yusuf Mohsinally6c078ae2013-11-21 11:06:42 -080085 if present != value:
86 self._servo_saved_state.append('%s:%s' % (key, present))
87 self._servo.set(key, value)
Tom Wai-Hong Tama8d4a102015-10-10 10:43:20 +080088 time.sleep(self._servo_prog_state_delay)
Yusuf Mohsinally6c078ae2013-11-21 11:06:42 -080089
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)
Dan Shifecdaf42015-07-28 10:17:26 -0700105 self._servo.system(self._program_cmd,
106 timeout=FIRMWARE_PROGRAM_TIMEOUT_SEC)
Yusuf Mohsinally6c078ae2013-11-21 11:06:42 -0800107 finally:
108 self._restore_servo_state()
109
110
111class FlashromProgrammer(_BaseProgrammer):
112 """Class for programming AP flashrom."""
113
Tom Wai-Hong Tam73f14ed22016-01-08 02:21:43 +0800114 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 """
Yusuf Mohsinally6c078ae2013-11-21 11:06:42 -0800120 super(FlashromProgrammer, self).__init__(servo, ['flashrom',])
Tom Wai-Hong Tam73f14ed22016-01-08 02:21:43 +0800121 self._keep_ro = keep_ro
Yusuf Mohsinally6c078ae2013-11-21 11:06:42 -0800122 self._fw_path = None
123 self._tmp_path = '/tmp'
124 self._fw_main = os.path.join(self._tmp_path, 'fw_main')
Tom Wai-Hong Tam73f14ed22016-01-08 02:21:43 +0800125 self._wp_ro = os.path.join(self._tmp_path, 'wp_ro')
Yusuf Mohsinally6c078ae2013-11-21 11:06:42 -0800126 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
130
131 def program(self):
132 """Program the firmware but preserve VPD and HWID."""
133 assert self._fw_path is not None
134 self._set_servo_state()
135 try:
Tom Wai-Hong Tam73f14ed22016-01-08 02:21:43 +0800136 wp_ro_section = [('WP_RO', self._wp_ro)]
137 rw_vpd_section = [('RW_VPD', self._rw_vpd)]
138 ro_vpd_section = [('RO_VPD', self._ro_vpd)]
Yusuf Mohsinally6c078ae2013-11-21 11:06:42 -0800139 gbb_section = [('GBB', self._gbb)]
Tom Wai-Hong Tam73f14ed22016-01-08 02:21:43 +0800140 if self._keep_ro:
141 # Keep the whole RO portion
142 preserved_sections = wp_ro_section + rw_vpd_section
143 else:
144 preserved_sections = ro_vpd_section + rw_vpd_section
145
Dan Shia5fef052015-05-18 23:28:47 -0700146 servo_version = self._servo.get_servo_version()
147 servo_v2_programmer = 'ft2232_spi:type=servo-v2'
148 servo_v3_programmer = 'linux_spi'
Kevin Chengdf2e29f2016-09-09 02:31:22 -0700149 servo_v4_programmer = 'raiden_spi'
Dan Shia5fef052015-05-18 23:28:47 -0700150 if servo_version == 'servo_v2':
151 programmer = servo_v2_programmer
152 if self._servo.servo_serial:
153 programmer += ',serial=%s' % self._servo.servo_serial
154 elif servo_version == 'servo_v3':
155 programmer = servo_v3_programmer
Kevin Chengdf2e29f2016-09-09 02:31:22 -0700156 elif servo_version == 'servo_v4':
157 programmer = servo_v4_programmer
158 # Get the serial of the servo micro if it exists.
159 servo_serials = self._servo._server.get_servo_serials()
160 servo_micro_serial = servo_serials.get('servo_micro')
161 if servo_micro_serial:
162 programmer += ':serial=%s' % servo_micro_serial
Dan Shia5fef052015-05-18 23:28:47 -0700163 else:
164 raise Exception('Servo version %s is not supported.' %
165 servo_version)
Yusuf Mohsinally6c078ae2013-11-21 11:06:42 -0800166 # Save needed sections from current firmware
Tom Wai-Hong Tam73f14ed22016-01-08 02:21:43 +0800167 for section in preserved_sections + gbb_section:
Yusuf Mohsinally6c078ae2013-11-21 11:06:42 -0800168 self._servo.system(' '.join([
Dan Shia5fef052015-05-18 23:28:47 -0700169 'flashrom', '-V', '-p', programmer,
Dan Shifecdaf42015-07-28 10:17:26 -0700170 '-r', self._fw_main, '-i', '%s:%s' % section]),
171 timeout=FIRMWARE_PROGRAM_TIMEOUT_SEC)
Yusuf Mohsinally6c078ae2013-11-21 11:06:42 -0800172
173 # Pack the saved VPD into new firmware
174 self._servo.system('cp %s %s' % (self._fw_path, self._fw_main))
175 img_size = self._servo.system_output(
176 "stat -c '%%s' %s" % self._fw_main)
177 pack_cmd = ['flashrom',
178 '-p', 'dummy:image=%s,size=%s,emulate=VARIABLE_SIZE' % (
179 self._fw_main, img_size),
180 '-w', self._fw_main]
Tom Wai-Hong Tam73f14ed22016-01-08 02:21:43 +0800181 for section in preserved_sections:
Yusuf Mohsinally6c078ae2013-11-21 11:06:42 -0800182 pack_cmd.extend(['-i', '%s:%s' % section])
Dan Shifecdaf42015-07-28 10:17:26 -0700183 self._servo.system(' '.join(pack_cmd),
184 timeout=FIRMWARE_PROGRAM_TIMEOUT_SEC)
Yusuf Mohsinally6c078ae2013-11-21 11:06:42 -0800185
Tom Wai-Hong Tam73f14ed22016-01-08 02:21:43 +0800186 # HWID is inside the RO portion. Don't preserve HWID if we keep RO.
187 if not self._keep_ro:
188 # Read original HWID. The output format is:
189 # hardware_id: RAMBI TEST A_A 0128
190 gbb_hwid_output = self._servo.system_output(
191 'gbb_utility -g --hwid %s' % self._gbb)
192 original_hwid = gbb_hwid_output.split(':', 1)[1].strip()
Yusuf Mohsinally6c078ae2013-11-21 11:06:42 -0800193
Tom Wai-Hong Tam73f14ed22016-01-08 02:21:43 +0800194 # Write HWID to new firmware
195 self._servo.system("gbb_utility -s --hwid='%s' %s" %
196 (original_hwid, self._fw_main))
Yusuf Mohsinally6c078ae2013-11-21 11:06:42 -0800197
198 # Flash the new firmware
199 self._servo.system(' '.join([
Dan Shia5fef052015-05-18 23:28:47 -0700200 'flashrom', '-V', '-p', programmer,
Dan Shifecdaf42015-07-28 10:17:26 -0700201 '-w', self._fw_main]), timeout=FIRMWARE_PROGRAM_TIMEOUT_SEC)
Yusuf Mohsinally6c078ae2013-11-21 11:06:42 -0800202 finally:
Tom Wai-Hong Tam97678db2016-03-17 05:36:13 +0800203 self._servo.get_power_state_controller().reset()
Yusuf Mohsinally6c078ae2013-11-21 11:06:42 -0800204 self._restore_servo_state()
205
206
207 def prepare_programmer(self, path):
208 """Prepare programmer for programming.
209
210 @param path: a string, name of the file containing the firmware image.
211 @param board: a string, used to find servo voltage setting.
212 """
213 faft_config = FAFTConfig(self._servo.get_board())
214 self._fw_path = path
Tom Wai-Hong Tama8d4a102015-10-10 10:43:20 +0800215 self._servo_prog_state_delay = faft_config.servo_prog_state_delay
Yusuf Mohsinally6c078ae2013-11-21 11:06:42 -0800216 self._servo_prog_state = (
Vic Yang15c2dcb2014-06-02 10:31:54 -0700217 'spi2_vref:%s' % faft_config.spi_voltage,
Yusuf Mohsinally6c078ae2013-11-21 11:06:42 -0800218 'spi2_buf_en:on',
219 'spi2_buf_on_flex_en:on',
220 'spi_hold:off',
221 'cold_reset:on',
Tom Wai-Hong Tama8d4a102015-10-10 10:43:20 +0800222 'usbpd_reset:on',
Yusuf Mohsinally6c078ae2013-11-21 11:06:42 -0800223 )
224
225
226class FlashECProgrammer(_BaseProgrammer):
227 """Class for programming AP flashrom."""
228
229 def __init__(self, servo):
230 """Configure required servo state."""
231 super(FlashECProgrammer, self).__init__(servo, ['flash_ec',])
232 self._servo = servo
233
234
Tom Wai-Hong Tam7b7eeac2015-07-23 01:53:52 +0800235 def prepare_programmer(self, image):
Yusuf Mohsinally6c078ae2013-11-21 11:06:42 -0800236 """Prepare programmer for programming.
237
238 @param image: string with the location of the image file
239 """
Kevin Chengdf2e29f2016-09-09 02:31:22 -0700240 port = self._servo._servo_host.servo_port
241 self._program_cmd = ('flash_ec --chip=%s --image=%s --port=%d' %
242 (self._servo.get('ec_chip'), image, port))
Yusuf Mohsinally6c078ae2013-11-21 11:06:42 -0800243
244
245class ProgrammerV2(object):
Dan Shia5fef052015-05-18 23:28:47 -0700246 """Main programmer class which provides programmer for BIOS and EC with
247 servo V2."""
Yusuf Mohsinally6c078ae2013-11-21 11:06:42 -0800248
249 def __init__(self, servo):
250 self._servo = servo
Ricky Liangc101a562014-05-15 10:56:15 +0800251 self._valid_boards = self._get_valid_v2_boards()
Yusuf Mohsinally6c078ae2013-11-21 11:06:42 -0800252 self._bios_programmer = self._factory_bios(self._servo)
253 self._ec_programmer = self._factory_ec(self._servo)
254
255
Ricky Liangc101a562014-05-15 10:56:15 +0800256 @staticmethod
257 def _get_valid_v2_boards():
258 """Greps servod config files to look for valid v2 boards.
259
260 @return A list of valid board names.
261 """
Ricky Liangc7cc3402014-05-24 00:25:26 +0800262 site_packages_paths = site.getsitepackages()
263 SERVOD_CONFIG_DATA_DIR = None
264 for p in site_packages_paths:
265 servo_data_path = os.path.join(p, 'servo', 'data')
266 if os.path.exists(servo_data_path):
267 SERVOD_CONFIG_DATA_DIR = servo_data_path
268 break
269 if not SERVOD_CONFIG_DATA_DIR:
270 raise ProgrammerError(
271 'Unable to locate data directory of Python servo module')
Ricky Liangc101a562014-05-15 10:56:15 +0800272 SERVOFLEX_V2_R0_P50_CONFIG = 'servoflex_v2_r0_p50.xml'
Ricky Liangf19ab9f2014-06-11 15:50:44 +0800273 SERVO_CONFIG_GLOB = 'servo_*_overlay.xml'
Ricky Liangc101a562014-05-15 10:56:15 +0800274 SERVO_CONFIG_REGEXP = 'servo_(?P<board>.+)_overlay.xml'
275
Ricky Liangf19ab9f2014-06-11 15:50:44 +0800276 def is_v2_compatible_board(board_config_path):
277 """Check if the given board config file is v2-compatible.
278
279 @param board_config_path: Path to a board config XML file.
280
281 @return True if the board is v2-compatible; False otherwise.
282 """
283 configs = []
284 def get_all_includes(config_path):
285 """Get all included XML config names in the given config file.
286
287 @param config_path: Path to a servo config file.
288 """
289 root = xml.etree.ElementTree.parse(config_path).getroot()
290 for element in root.findall('include'):
291 include_name = element.find('name').text
292 configs.append(include_name)
293 get_all_includes(os.path.join(
294 SERVOD_CONFIG_DATA_DIR, include_name))
295
296 get_all_includes(board_config_path)
297 return True if SERVOFLEX_V2_R0_P50_CONFIG in configs else False
298
Ricky Liangc101a562014-05-15 10:56:15 +0800299 result = []
Ricky Liangf19ab9f2014-06-11 15:50:44 +0800300 board_overlays = glob.glob(
301 os.path.join(SERVOD_CONFIG_DATA_DIR, SERVO_CONFIG_GLOB))
302 for overlay_path in board_overlays:
303 if is_v2_compatible_board(overlay_path):
304 result.append(re.search(SERVO_CONFIG_REGEXP,
305 overlay_path).group('board'))
Ricky Liangc101a562014-05-15 10:56:15 +0800306 return result
307
308
Wai-Hong Tamc36c4d22016-09-09 10:39:45 -0700309 def _get_flashrom_programmer(self, servo):
310 """Gets a proper flashrom programmer.
311
312 @param servo: A servo object.
313
314 @return A programmer for flashrom.
315 """
316 return FlashromProgrammer(servo)
317
318
Yusuf Mohsinally6c078ae2013-11-21 11:06:42 -0800319 def _factory_bios(self, servo):
320 """Instantiates and returns (bios, ec) programmers for the board.
321
322 @param servo: A servo object.
323
324 @return A programmer for ec. If the programmer is not supported
325 for the board, None will be returned.
326 """
327 _bios_prog = None
328 _board = servo.get_board()
329
Yusuf Mohsinally6c078ae2013-11-21 11:06:42 -0800330 logging.debug('Setting up BIOS programmer for board: %s', _board)
Ricky Liangc101a562014-05-15 10:56:15 +0800331 if _board in self._valid_boards:
Wai-Hong Tamc36c4d22016-09-09 10:39:45 -0700332 _bios_prog = self._get_flashrom_programmer(servo)
Yusuf Mohsinally6c078ae2013-11-21 11:06:42 -0800333 else:
Ilja H. Friedel04be2bd2014-05-07 21:29:59 -0700334 logging.warning('No BIOS programmer found for board: %s', _board)
Yusuf Mohsinally6c078ae2013-11-21 11:06:42 -0800335
336 return _bios_prog
337
338
339 def _factory_ec(self, servo):
340 """Instantiates and returns ec programmer for the board.
341
342 @param servo: A servo object.
343
344 @return A programmer for ec. If the programmer is not supported
345 for the board, None will be returned.
346 """
347 _ec_prog = None
348 _board = servo.get_board()
349
350 logging.debug('Setting up EC programmer for board: %s', _board)
Ricky Liangc101a562014-05-15 10:56:15 +0800351 if _board in self._valid_boards:
Yusuf Mohsinally6c078ae2013-11-21 11:06:42 -0800352 _ec_prog = FlashECProgrammer(servo)
353 else:
Ilja H. Friedel04be2bd2014-05-07 21:29:59 -0700354 logging.warning('No EC programmer found for board: %s', _board)
Yusuf Mohsinally6c078ae2013-11-21 11:06:42 -0800355
356 return _ec_prog
357
358
359 def program_bios(self, image):
360 """Programs the DUT with provide bios image.
361
362 @param image: (required) location of bios image file.
363
364 """
365 self._bios_programmer.prepare_programmer(image)
366 self._bios_programmer.program()
367
Dan Shia5fef052015-05-18 23:28:47 -0700368
Yusuf Mohsinally6c078ae2013-11-21 11:06:42 -0800369 def program_ec(self, image):
370 """Programs the DUT with provide ec image.
371
372 @param image: (required) location of ec image file.
373
374 """
375 self._ec_programmer.prepare_programmer(image)
376 self._ec_programmer.program()
Dan Shia5fef052015-05-18 23:28:47 -0700377
378
Wai-Hong Tamc36c4d22016-09-09 10:39:45 -0700379class ProgrammerV2RwOnly(ProgrammerV2):
380 """Main programmer class which provides programmer for only updating the RW
381 portion of BIOS with servo V2.
382
383 It does nothing on EC, as EC software sync on the next boot will
384 automatically overwrite the EC RW portion, using the EC RW image inside
385 the BIOS RW image.
386
387 """
388
389 def _get_flashrom_programmer(self, servo):
390 """Gets a proper flashrom programmer.
391
392 @param servo: A servo object.
393
394 @return A programmer for flashrom.
395 """
396 return FlashromProgrammer(servo, keep_ro=True)
397
398
399 def program_ec(self, image):
400 """Programs the DUT with provide ec image.
401
402 @param image: (required) location of ec image file.
403
404 """
405 # Do nothing. EC software sync will update the EC RW.
406 pass
407
408
Dan Shia5fef052015-05-18 23:28:47 -0700409class ProgrammerV3(object):
410 """Main programmer class which provides programmer for BIOS and EC with
411 servo V3.
412
413 Different from programmer for servo v2, programmer for servo v3 does not
414 try to validate if the board can use servo V3 to update firmware. As long as
415 the servod process running in beagblebone with given board, the program will
416 attempt to flash bios and ec.
417
418 """
419
420 def __init__(self, servo):
421 self._servo = servo
422 self._bios_programmer = FlashromProgrammer(servo)
423 self._ec_programmer = FlashECProgrammer(servo)
424
425
426 def program_bios(self, image):
427 """Programs the DUT with provide bios image.
428
429 @param image: (required) location of bios image file.
430
431 """
432 self._bios_programmer.prepare_programmer(image)
433 self._bios_programmer.program()
434
435
Tom Wai-Hong Tam7b7eeac2015-07-23 01:53:52 +0800436 def program_ec(self, image):
Dan Shia5fef052015-05-18 23:28:47 -0700437 """Programs the DUT with provide ec image.
438
439 @param image: (required) location of ec image file.
Dan Shia5fef052015-05-18 23:28:47 -0700440
441 """
Tom Wai-Hong Tam7b7eeac2015-07-23 01:53:52 +0800442 self._ec_programmer.prepare_programmer(image)
Dan Shia5fef052015-05-18 23:28:47 -0700443 self._ec_programmer.program()
Tom Wai-Hong Tam4ac78982016-01-08 02:34:37 +0800444
445
Kevin Chengdf2e29f2016-09-09 02:31:22 -0700446class ProgrammerV3RwOnly(ProgrammerV3):
Tom Wai-Hong Tam4ac78982016-01-08 02:34:37 +0800447 """Main programmer class which provides programmer for only updating the RW
448 portion of BIOS with servo V3.
449
450 It does nothing on EC, as EC software sync on the next boot will
451 automatically overwrite the EC RW portion, using the EC RW image inside
452 the BIOS RW image.
453
454 """
455
456 def __init__(self, servo):
457 self._servo = servo
458 self._bios_programmer = FlashromProgrammer(servo, keep_ro=True)
459
460
Tom Wai-Hong Tam4ac78982016-01-08 02:34:37 +0800461 def program_ec(self, image):
462 """Programs the DUT with provide ec image.
463
464 @param image: (required) location of ec image file.
465
466 """
467 # Do nothing. EC software sync will update the EC RW.
468 pass