blob: 216e3443469a3ebf7258e863ec3321ff469300ba [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
114 def __init__(self, servo):
115 """Configure required servo state."""
116 super(FlashromProgrammer, self).__init__(servo, ['flashrom',])
117 self._fw_path = None
118 self._tmp_path = '/tmp'
119 self._fw_main = os.path.join(self._tmp_path, 'fw_main')
120 self._ro_vpd = os.path.join(self._tmp_path, 'ro_vpd')
121 self._rw_vpd = os.path.join(self._tmp_path, 'rw_vpd')
122 self._gbb = os.path.join(self._tmp_path, 'gbb')
123
124
125 def program(self):
126 """Program the firmware but preserve VPD and HWID."""
127 assert self._fw_path is not None
128 self._set_servo_state()
129 try:
130 vpd_sections = [('RW_VPD', self._rw_vpd), ('RO_VPD', self._ro_vpd)]
131 gbb_section = [('GBB', self._gbb)]
Dan Shia5fef052015-05-18 23:28:47 -0700132 servo_version = self._servo.get_servo_version()
133 servo_v2_programmer = 'ft2232_spi:type=servo-v2'
134 servo_v3_programmer = 'linux_spi'
135 if servo_version == 'servo_v2':
136 programmer = servo_v2_programmer
137 if self._servo.servo_serial:
138 programmer += ',serial=%s' % self._servo.servo_serial
139 elif servo_version == 'servo_v3':
140 programmer = servo_v3_programmer
141 else:
142 raise Exception('Servo version %s is not supported.' %
143 servo_version)
Yusuf Mohsinally6c078ae2013-11-21 11:06:42 -0800144 # Save needed sections from current firmware
145 for section in vpd_sections + gbb_section:
146 self._servo.system(' '.join([
Dan Shia5fef052015-05-18 23:28:47 -0700147 'flashrom', '-V', '-p', programmer,
Dan Shifecdaf42015-07-28 10:17:26 -0700148 '-r', self._fw_main, '-i', '%s:%s' % section]),
149 timeout=FIRMWARE_PROGRAM_TIMEOUT_SEC)
Yusuf Mohsinally6c078ae2013-11-21 11:06:42 -0800150
151 # Pack the saved VPD into new firmware
152 self._servo.system('cp %s %s' % (self._fw_path, self._fw_main))
153 img_size = self._servo.system_output(
154 "stat -c '%%s' %s" % self._fw_main)
155 pack_cmd = ['flashrom',
156 '-p', 'dummy:image=%s,size=%s,emulate=VARIABLE_SIZE' % (
157 self._fw_main, img_size),
158 '-w', self._fw_main]
159 for section in vpd_sections:
160 pack_cmd.extend(['-i', '%s:%s' % section])
Dan Shifecdaf42015-07-28 10:17:26 -0700161 self._servo.system(' '.join(pack_cmd),
162 timeout=FIRMWARE_PROGRAM_TIMEOUT_SEC)
Yusuf Mohsinally6c078ae2013-11-21 11:06:42 -0800163
164 # Read original HWID. The output format is:
165 # hardware_id: RAMBI TEST A_A 0128
166 gbb_hwid_output = self._servo.system_output(
167 'gbb_utility -g --hwid %s' % self._gbb)
168 original_hwid = gbb_hwid_output.split(':', 1)[1].strip()
169
170 # Write HWID to new firmware
171 self._servo.system("gbb_utility -s --hwid='%s' %s" %
172 (original_hwid, self._fw_main))
173
174 # Flash the new firmware
175 self._servo.system(' '.join([
Dan Shia5fef052015-05-18 23:28:47 -0700176 'flashrom', '-V', '-p', programmer,
Dan Shifecdaf42015-07-28 10:17:26 -0700177 '-w', self._fw_main]), timeout=FIRMWARE_PROGRAM_TIMEOUT_SEC)
Yusuf Mohsinally6c078ae2013-11-21 11:06:42 -0800178 finally:
179 self._restore_servo_state()
180
181
182 def prepare_programmer(self, path):
183 """Prepare programmer for programming.
184
185 @param path: a string, name of the file containing the firmware image.
186 @param board: a string, used to find servo voltage setting.
187 """
188 faft_config = FAFTConfig(self._servo.get_board())
189 self._fw_path = path
Tom Wai-Hong Tama8d4a102015-10-10 10:43:20 +0800190 self._servo_prog_state_delay = faft_config.servo_prog_state_delay
Yusuf Mohsinally6c078ae2013-11-21 11:06:42 -0800191 self._servo_prog_state = (
Vic Yang15c2dcb2014-06-02 10:31:54 -0700192 'spi2_vref:%s' % faft_config.spi_voltage,
Yusuf Mohsinally6c078ae2013-11-21 11:06:42 -0800193 'spi2_buf_en:on',
194 'spi2_buf_on_flex_en:on',
195 'spi_hold:off',
196 'cold_reset:on',
Tom Wai-Hong Tama8d4a102015-10-10 10:43:20 +0800197 'usbpd_reset:on',
Yusuf Mohsinally6c078ae2013-11-21 11:06:42 -0800198 )
199
200
201class FlashECProgrammer(_BaseProgrammer):
202 """Class for programming AP flashrom."""
203
204 def __init__(self, servo):
205 """Configure required servo state."""
206 super(FlashECProgrammer, self).__init__(servo, ['flash_ec',])
207 self._servo = servo
208
209
Tom Wai-Hong Tam7b7eeac2015-07-23 01:53:52 +0800210 def prepare_programmer(self, image):
Yusuf Mohsinally6c078ae2013-11-21 11:06:42 -0800211 """Prepare programmer for programming.
212
213 @param image: string with the location of the image file
214 """
215 # TODO: need to not have port be hardcoded
Tom Wai-Hong Tam7b7eeac2015-07-23 01:53:52 +0800216 self._program_cmd = ('flash_ec --chip=%s --image=%s --port=%s' %
217 (self._servo.get('ec_chip'), image, '9999'))
Yusuf Mohsinally6c078ae2013-11-21 11:06:42 -0800218
219
220class ProgrammerV2(object):
Dan Shia5fef052015-05-18 23:28:47 -0700221 """Main programmer class which provides programmer for BIOS and EC with
222 servo V2."""
Yusuf Mohsinally6c078ae2013-11-21 11:06:42 -0800223
224 def __init__(self, servo):
225 self._servo = servo
Ricky Liangc101a562014-05-15 10:56:15 +0800226 self._valid_boards = self._get_valid_v2_boards()
Yusuf Mohsinally6c078ae2013-11-21 11:06:42 -0800227 self._bios_programmer = self._factory_bios(self._servo)
228 self._ec_programmer = self._factory_ec(self._servo)
229
230
Ricky Liangc101a562014-05-15 10:56:15 +0800231 @staticmethod
232 def _get_valid_v2_boards():
233 """Greps servod config files to look for valid v2 boards.
234
235 @return A list of valid board names.
236 """
Ricky Liangc7cc3402014-05-24 00:25:26 +0800237 site_packages_paths = site.getsitepackages()
238 SERVOD_CONFIG_DATA_DIR = None
239 for p in site_packages_paths:
240 servo_data_path = os.path.join(p, 'servo', 'data')
241 if os.path.exists(servo_data_path):
242 SERVOD_CONFIG_DATA_DIR = servo_data_path
243 break
244 if not SERVOD_CONFIG_DATA_DIR:
245 raise ProgrammerError(
246 'Unable to locate data directory of Python servo module')
Ricky Liangc101a562014-05-15 10:56:15 +0800247 SERVOFLEX_V2_R0_P50_CONFIG = 'servoflex_v2_r0_p50.xml'
Ricky Liangf19ab9f2014-06-11 15:50:44 +0800248 SERVO_CONFIG_GLOB = 'servo_*_overlay.xml'
Ricky Liangc101a562014-05-15 10:56:15 +0800249 SERVO_CONFIG_REGEXP = 'servo_(?P<board>.+)_overlay.xml'
250
Ricky Liangf19ab9f2014-06-11 15:50:44 +0800251 def is_v2_compatible_board(board_config_path):
252 """Check if the given board config file is v2-compatible.
253
254 @param board_config_path: Path to a board config XML file.
255
256 @return True if the board is v2-compatible; False otherwise.
257 """
258 configs = []
259 def get_all_includes(config_path):
260 """Get all included XML config names in the given config file.
261
262 @param config_path: Path to a servo config file.
263 """
264 root = xml.etree.ElementTree.parse(config_path).getroot()
265 for element in root.findall('include'):
266 include_name = element.find('name').text
267 configs.append(include_name)
268 get_all_includes(os.path.join(
269 SERVOD_CONFIG_DATA_DIR, include_name))
270
271 get_all_includes(board_config_path)
272 return True if SERVOFLEX_V2_R0_P50_CONFIG in configs else False
273
Ricky Liangc101a562014-05-15 10:56:15 +0800274 result = []
Ricky Liangf19ab9f2014-06-11 15:50:44 +0800275 board_overlays = glob.glob(
276 os.path.join(SERVOD_CONFIG_DATA_DIR, SERVO_CONFIG_GLOB))
277 for overlay_path in board_overlays:
278 if is_v2_compatible_board(overlay_path):
279 result.append(re.search(SERVO_CONFIG_REGEXP,
280 overlay_path).group('board'))
Ricky Liangc101a562014-05-15 10:56:15 +0800281 return result
282
283
Yusuf Mohsinally6c078ae2013-11-21 11:06:42 -0800284 def _factory_bios(self, servo):
285 """Instantiates and returns (bios, ec) programmers for the board.
286
287 @param servo: A servo object.
288
289 @return A programmer for ec. If the programmer is not supported
290 for the board, None will be returned.
291 """
292 _bios_prog = None
293 _board = servo.get_board()
294
295 servo_prog_state = [
296 'spi2_buf_en:on',
297 'spi2_buf_on_flex_en:on',
298 'spi_hold:off',
299 'cold_reset:on',
300 ]
301
302 logging.debug('Setting up BIOS programmer for board: %s', _board)
Ricky Liangc101a562014-05-15 10:56:15 +0800303 if _board in self._valid_boards:
Yusuf Mohsinally6c078ae2013-11-21 11:06:42 -0800304 _bios_prog = FlashromProgrammer(servo)
305 else:
Ilja H. Friedel04be2bd2014-05-07 21:29:59 -0700306 logging.warning('No BIOS programmer found for board: %s', _board)
Yusuf Mohsinally6c078ae2013-11-21 11:06:42 -0800307
308 return _bios_prog
309
310
311 def _factory_ec(self, servo):
312 """Instantiates and returns ec programmer for the board.
313
314 @param servo: A servo object.
315
316 @return A programmer for ec. If the programmer is not supported
317 for the board, None will be returned.
318 """
319 _ec_prog = None
320 _board = servo.get_board()
321
322 logging.debug('Setting up EC programmer for board: %s', _board)
Ricky Liangc101a562014-05-15 10:56:15 +0800323 if _board in self._valid_boards:
Yusuf Mohsinally6c078ae2013-11-21 11:06:42 -0800324 _ec_prog = FlashECProgrammer(servo)
325 else:
Ilja H. Friedel04be2bd2014-05-07 21:29:59 -0700326 logging.warning('No EC programmer found for board: %s', _board)
Yusuf Mohsinally6c078ae2013-11-21 11:06:42 -0800327
328 return _ec_prog
329
330
331 def program_bios(self, image):
332 """Programs the DUT with provide bios image.
333
334 @param image: (required) location of bios image file.
335
336 """
337 self._bios_programmer.prepare_programmer(image)
338 self._bios_programmer.program()
339
Dan Shia5fef052015-05-18 23:28:47 -0700340
Yusuf Mohsinally6c078ae2013-11-21 11:06:42 -0800341 def program_ec(self, image):
342 """Programs the DUT with provide ec image.
343
344 @param image: (required) location of ec image file.
345
346 """
347 self._ec_programmer.prepare_programmer(image)
348 self._ec_programmer.program()
Dan Shia5fef052015-05-18 23:28:47 -0700349
350
351class ProgrammerV3(object):
352 """Main programmer class which provides programmer for BIOS and EC with
353 servo V3.
354
355 Different from programmer for servo v2, programmer for servo v3 does not
356 try to validate if the board can use servo V3 to update firmware. As long as
357 the servod process running in beagblebone with given board, the program will
358 attempt to flash bios and ec.
359
360 """
361
362 def __init__(self, servo):
363 self._servo = servo
364 self._bios_programmer = FlashromProgrammer(servo)
365 self._ec_programmer = FlashECProgrammer(servo)
366
367
368 def program_bios(self, image):
369 """Programs the DUT with provide bios image.
370
371 @param image: (required) location of bios image file.
372
373 """
374 self._bios_programmer.prepare_programmer(image)
375 self._bios_programmer.program()
376
377
Tom Wai-Hong Tam7b7eeac2015-07-23 01:53:52 +0800378 def program_ec(self, image):
Dan Shia5fef052015-05-18 23:28:47 -0700379 """Programs the DUT with provide ec image.
380
381 @param image: (required) location of ec image file.
Dan Shia5fef052015-05-18 23:28:47 -0700382
383 """
Tom Wai-Hong Tam7b7eeac2015-07-23 01:53:52 +0800384 self._ec_programmer.prepare_programmer(image)
Dan Shia5fef052015-05-18 23:28:47 -0700385 self._ec_programmer.program()