blob: 4d7c166b13e60ebc2bf3e6fde7de52ff50583587 [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
Ricky Liangf19ab9f2014-06-11 15:50:44 +080019import xml.etree.ElementTree
Yusuf Mohsinally6c078ae2013-11-21 11:06:42 -080020
Yusuf Mohsinally8c4631d2014-05-05 18:20:54 -070021from autotest_lib.client.common_lib import error
Yusuf Mohsinally6c078ae2013-11-21 11:06:42 -080022from autotest_lib.server.cros.faft.config.config import Config as FAFTConfig
23
24
Dan Shifecdaf42015-07-28 10:17:26 -070025# Number of seconds for program EC/BIOS to time out.
26FIRMWARE_PROGRAM_TIMEOUT_SEC = 600
27
Yusuf Mohsinally6c078ae2013-11-21 11:06:42 -080028class ProgrammerError(Exception):
29 """Local exception class wrapper."""
30 pass
31
32
33class _BaseProgrammer(object):
34 """Class implementing base programmer services.
35
36 Private attributes:
37 _servo: a servo object controlling the servo device
38 _servo_prog_state: a tuple of strings of "<control>:<value>" pairs,
39 listing servo controls and their required values for
40 programming
41 _servo_saved_state: a list of the same elements as _servo_prog_state,
42 those which need to be restored after programming
43 _program_cmd: a string, the shell command to run on the servo host
44 to actually program the firmware. Dependent on
45 firmware/hardware type, set by subclasses.
46 """
47
48 def __init__(self, servo, req_list):
49 """Base constructor.
50 @param servo: a servo object controlling the servo device
51 @param req_list: a list of strings, names of the utilities required
52 to be in the path for the programmer to succeed
53 """
54 self._servo = servo
55 self._servo_prog_state = ()
56 self._servo_saved_state = []
57 self._program_cmd = ''
Yusuf Mohsinally8c4631d2014-05-05 18:20:54 -070058 try:
59 self._servo.system('which %s' % ' '.join(req_list))
60 except error.AutoservRunError:
61 # TODO: We turn this exception into a warn since the fw programmer
62 # is not working right now, and some systems do not package the
63 # required utilities its checking for.
64 # We should reinstate this exception once the programmer is working
65 # to indicate the missing utilities earlier in the test cycle.
66 # Bug chromium:371011 filed to track this.
67 logging.warn("Ignoring exception when verify required bins : %s",
68 ' '.join(req_list))
Yusuf Mohsinally6c078ae2013-11-21 11:06:42 -080069
Dan Shia5fef052015-05-18 23:28:47 -070070
Yusuf Mohsinally6c078ae2013-11-21 11:06:42 -080071 def _set_servo_state(self):
72 """Set servo for programming, while saving the current state."""
73 logging.debug("Setting servo state for programming")
74 for item in self._servo_prog_state:
75 key, value = item.split(':')
76 present = self._servo.get(key)
77 if present != value:
78 self._servo_saved_state.append('%s:%s' % (key, present))
79 self._servo.set(key, value)
80
81
82 def _restore_servo_state(self):
83 """Restore previously saved servo state."""
84 logging.debug("Restoring servo state after programming")
85 self._servo_saved_state.reverse() # Do it in the reverse order.
86 for item in self._servo_saved_state:
87 key, value = item.split(':')
88 self._servo.set(key, value)
89
90
91 def program(self):
92 """Program the firmware as configured by a subclass."""
93 self._set_servo_state()
94 try:
95 logging.debug("Programmer command: %s", self._program_cmd)
Dan Shifecdaf42015-07-28 10:17:26 -070096 self._servo.system(self._program_cmd,
97 timeout=FIRMWARE_PROGRAM_TIMEOUT_SEC)
Yusuf Mohsinally6c078ae2013-11-21 11:06:42 -080098 finally:
99 self._restore_servo_state()
100
101
102class FlashromProgrammer(_BaseProgrammer):
103 """Class for programming AP flashrom."""
104
105 def __init__(self, servo):
106 """Configure required servo state."""
107 super(FlashromProgrammer, self).__init__(servo, ['flashrom',])
108 self._fw_path = None
109 self._tmp_path = '/tmp'
110 self._fw_main = os.path.join(self._tmp_path, 'fw_main')
111 self._ro_vpd = os.path.join(self._tmp_path, 'ro_vpd')
112 self._rw_vpd = os.path.join(self._tmp_path, 'rw_vpd')
113 self._gbb = os.path.join(self._tmp_path, 'gbb')
114
115
116 def program(self):
117 """Program the firmware but preserve VPD and HWID."""
118 assert self._fw_path is not None
119 self._set_servo_state()
120 try:
121 vpd_sections = [('RW_VPD', self._rw_vpd), ('RO_VPD', self._ro_vpd)]
122 gbb_section = [('GBB', self._gbb)]
Dan Shia5fef052015-05-18 23:28:47 -0700123 servo_version = self._servo.get_servo_version()
124 servo_v2_programmer = 'ft2232_spi:type=servo-v2'
125 servo_v3_programmer = 'linux_spi'
126 if servo_version == 'servo_v2':
127 programmer = servo_v2_programmer
128 if self._servo.servo_serial:
129 programmer += ',serial=%s' % self._servo.servo_serial
130 elif servo_version == 'servo_v3':
131 programmer = servo_v3_programmer
132 else:
133 raise Exception('Servo version %s is not supported.' %
134 servo_version)
Yusuf Mohsinally6c078ae2013-11-21 11:06:42 -0800135 # Save needed sections from current firmware
136 for section in vpd_sections + gbb_section:
137 self._servo.system(' '.join([
Dan Shia5fef052015-05-18 23:28:47 -0700138 'flashrom', '-V', '-p', programmer,
Dan Shifecdaf42015-07-28 10:17:26 -0700139 '-r', self._fw_main, '-i', '%s:%s' % section]),
140 timeout=FIRMWARE_PROGRAM_TIMEOUT_SEC)
Yusuf Mohsinally6c078ae2013-11-21 11:06:42 -0800141
142 # Pack the saved VPD into new firmware
143 self._servo.system('cp %s %s' % (self._fw_path, self._fw_main))
144 img_size = self._servo.system_output(
145 "stat -c '%%s' %s" % self._fw_main)
146 pack_cmd = ['flashrom',
147 '-p', 'dummy:image=%s,size=%s,emulate=VARIABLE_SIZE' % (
148 self._fw_main, img_size),
149 '-w', self._fw_main]
150 for section in vpd_sections:
151 pack_cmd.extend(['-i', '%s:%s' % section])
Dan Shifecdaf42015-07-28 10:17:26 -0700152 self._servo.system(' '.join(pack_cmd),
153 timeout=FIRMWARE_PROGRAM_TIMEOUT_SEC)
Yusuf Mohsinally6c078ae2013-11-21 11:06:42 -0800154
155 # Read original HWID. The output format is:
156 # hardware_id: RAMBI TEST A_A 0128
157 gbb_hwid_output = self._servo.system_output(
158 'gbb_utility -g --hwid %s' % self._gbb)
159 original_hwid = gbb_hwid_output.split(':', 1)[1].strip()
160
161 # Write HWID to new firmware
162 self._servo.system("gbb_utility -s --hwid='%s' %s" %
163 (original_hwid, self._fw_main))
164
165 # Flash the new firmware
166 self._servo.system(' '.join([
Dan Shia5fef052015-05-18 23:28:47 -0700167 'flashrom', '-V', '-p', programmer,
Dan Shifecdaf42015-07-28 10:17:26 -0700168 '-w', self._fw_main]), timeout=FIRMWARE_PROGRAM_TIMEOUT_SEC)
Yusuf Mohsinally6c078ae2013-11-21 11:06:42 -0800169 finally:
170 self._restore_servo_state()
171
172
173 def prepare_programmer(self, path):
174 """Prepare programmer for programming.
175
176 @param path: a string, name of the file containing the firmware image.
177 @param board: a string, used to find servo voltage setting.
178 """
179 faft_config = FAFTConfig(self._servo.get_board())
180 self._fw_path = path
181 self._servo_prog_state = (
Vic Yang15c2dcb2014-06-02 10:31:54 -0700182 'spi2_vref:%s' % faft_config.spi_voltage,
Yusuf Mohsinally6c078ae2013-11-21 11:06:42 -0800183 'spi2_buf_en:on',
184 'spi2_buf_on_flex_en:on',
185 'spi_hold:off',
186 'cold_reset:on',
187 )
188
189
190class FlashECProgrammer(_BaseProgrammer):
191 """Class for programming AP flashrom."""
192
193 def __init__(self, servo):
194 """Configure required servo state."""
195 super(FlashECProgrammer, self).__init__(servo, ['flash_ec',])
196 self._servo = servo
197
198
Tom Wai-Hong Tam7b7eeac2015-07-23 01:53:52 +0800199 def prepare_programmer(self, image):
Yusuf Mohsinally6c078ae2013-11-21 11:06:42 -0800200 """Prepare programmer for programming.
201
202 @param image: string with the location of the image file
203 """
204 # TODO: need to not have port be hardcoded
Tom Wai-Hong Tam7b7eeac2015-07-23 01:53:52 +0800205 self._program_cmd = ('flash_ec --chip=%s --image=%s --port=%s' %
206 (self._servo.get('ec_chip'), image, '9999'))
Yusuf Mohsinally6c078ae2013-11-21 11:06:42 -0800207
208
209class ProgrammerV2(object):
Dan Shia5fef052015-05-18 23:28:47 -0700210 """Main programmer class which provides programmer for BIOS and EC with
211 servo V2."""
Yusuf Mohsinally6c078ae2013-11-21 11:06:42 -0800212
213 def __init__(self, servo):
214 self._servo = servo
Ricky Liangc101a562014-05-15 10:56:15 +0800215 self._valid_boards = self._get_valid_v2_boards()
Yusuf Mohsinally6c078ae2013-11-21 11:06:42 -0800216 self._bios_programmer = self._factory_bios(self._servo)
217 self._ec_programmer = self._factory_ec(self._servo)
218
219
Ricky Liangc101a562014-05-15 10:56:15 +0800220 @staticmethod
221 def _get_valid_v2_boards():
222 """Greps servod config files to look for valid v2 boards.
223
224 @return A list of valid board names.
225 """
Ricky Liangc7cc3402014-05-24 00:25:26 +0800226 site_packages_paths = site.getsitepackages()
227 SERVOD_CONFIG_DATA_DIR = None
228 for p in site_packages_paths:
229 servo_data_path = os.path.join(p, 'servo', 'data')
230 if os.path.exists(servo_data_path):
231 SERVOD_CONFIG_DATA_DIR = servo_data_path
232 break
233 if not SERVOD_CONFIG_DATA_DIR:
234 raise ProgrammerError(
235 'Unable to locate data directory of Python servo module')
Ricky Liangc101a562014-05-15 10:56:15 +0800236 SERVOFLEX_V2_R0_P50_CONFIG = 'servoflex_v2_r0_p50.xml'
Ricky Liangf19ab9f2014-06-11 15:50:44 +0800237 SERVO_CONFIG_GLOB = 'servo_*_overlay.xml'
Ricky Liangc101a562014-05-15 10:56:15 +0800238 SERVO_CONFIG_REGEXP = 'servo_(?P<board>.+)_overlay.xml'
239
Ricky Liangf19ab9f2014-06-11 15:50:44 +0800240 def is_v2_compatible_board(board_config_path):
241 """Check if the given board config file is v2-compatible.
242
243 @param board_config_path: Path to a board config XML file.
244
245 @return True if the board is v2-compatible; False otherwise.
246 """
247 configs = []
248 def get_all_includes(config_path):
249 """Get all included XML config names in the given config file.
250
251 @param config_path: Path to a servo config file.
252 """
253 root = xml.etree.ElementTree.parse(config_path).getroot()
254 for element in root.findall('include'):
255 include_name = element.find('name').text
256 configs.append(include_name)
257 get_all_includes(os.path.join(
258 SERVOD_CONFIG_DATA_DIR, include_name))
259
260 get_all_includes(board_config_path)
261 return True if SERVOFLEX_V2_R0_P50_CONFIG in configs else False
262
Ricky Liangc101a562014-05-15 10:56:15 +0800263 result = []
Ricky Liangf19ab9f2014-06-11 15:50:44 +0800264 board_overlays = glob.glob(
265 os.path.join(SERVOD_CONFIG_DATA_DIR, SERVO_CONFIG_GLOB))
266 for overlay_path in board_overlays:
267 if is_v2_compatible_board(overlay_path):
268 result.append(re.search(SERVO_CONFIG_REGEXP,
269 overlay_path).group('board'))
Ricky Liangc101a562014-05-15 10:56:15 +0800270 return result
271
272
Yusuf Mohsinally6c078ae2013-11-21 11:06:42 -0800273 def _factory_bios(self, servo):
274 """Instantiates and returns (bios, ec) programmers for the board.
275
276 @param servo: A servo object.
277
278 @return A programmer for ec. If the programmer is not supported
279 for the board, None will be returned.
280 """
281 _bios_prog = None
282 _board = servo.get_board()
283
284 servo_prog_state = [
285 'spi2_buf_en:on',
286 'spi2_buf_on_flex_en:on',
287 'spi_hold:off',
288 'cold_reset:on',
289 ]
290
291 logging.debug('Setting up BIOS programmer for board: %s', _board)
Ricky Liangc101a562014-05-15 10:56:15 +0800292 if _board in self._valid_boards:
Yusuf Mohsinally6c078ae2013-11-21 11:06:42 -0800293 _bios_prog = FlashromProgrammer(servo)
294 else:
Ilja H. Friedel04be2bd2014-05-07 21:29:59 -0700295 logging.warning('No BIOS programmer found for board: %s', _board)
Yusuf Mohsinally6c078ae2013-11-21 11:06:42 -0800296
297 return _bios_prog
298
299
300 def _factory_ec(self, servo):
301 """Instantiates and returns ec programmer for the board.
302
303 @param servo: A servo object.
304
305 @return A programmer for ec. If the programmer is not supported
306 for the board, None will be returned.
307 """
308 _ec_prog = None
309 _board = servo.get_board()
310
311 logging.debug('Setting up EC programmer for board: %s', _board)
Ricky Liangc101a562014-05-15 10:56:15 +0800312 if _board in self._valid_boards:
Yusuf Mohsinally6c078ae2013-11-21 11:06:42 -0800313 _ec_prog = FlashECProgrammer(servo)
314 else:
Ilja H. Friedel04be2bd2014-05-07 21:29:59 -0700315 logging.warning('No EC programmer found for board: %s', _board)
Yusuf Mohsinally6c078ae2013-11-21 11:06:42 -0800316
317 return _ec_prog
318
319
320 def program_bios(self, image):
321 """Programs the DUT with provide bios image.
322
323 @param image: (required) location of bios image file.
324
325 """
326 self._bios_programmer.prepare_programmer(image)
327 self._bios_programmer.program()
328
Dan Shia5fef052015-05-18 23:28:47 -0700329
Yusuf Mohsinally6c078ae2013-11-21 11:06:42 -0800330 def program_ec(self, image):
331 """Programs the DUT with provide ec image.
332
333 @param image: (required) location of ec image file.
334
335 """
336 self._ec_programmer.prepare_programmer(image)
337 self._ec_programmer.program()
Dan Shia5fef052015-05-18 23:28:47 -0700338
339
340class ProgrammerV3(object):
341 """Main programmer class which provides programmer for BIOS and EC with
342 servo V3.
343
344 Different from programmer for servo v2, programmer for servo v3 does not
345 try to validate if the board can use servo V3 to update firmware. As long as
346 the servod process running in beagblebone with given board, the program will
347 attempt to flash bios and ec.
348
349 """
350
351 def __init__(self, servo):
352 self._servo = servo
353 self._bios_programmer = FlashromProgrammer(servo)
354 self._ec_programmer = FlashECProgrammer(servo)
355
356
357 def program_bios(self, image):
358 """Programs the DUT with provide bios image.
359
360 @param image: (required) location of bios image file.
361
362 """
363 self._bios_programmer.prepare_programmer(image)
364 self._bios_programmer.program()
365
366
Tom Wai-Hong Tam7b7eeac2015-07-23 01:53:52 +0800367 def program_ec(self, image):
Dan Shia5fef052015-05-18 23:28:47 -0700368 """Programs the DUT with provide ec image.
369
370 @param image: (required) location of ec image file.
Dan Shia5fef052015-05-18 23:28:47 -0700371
372 """
Tom Wai-Hong Tam7b7eeac2015-07-23 01:53:52 +0800373 self._ec_programmer.prepare_programmer(image)
Dan Shia5fef052015-05-18 23:28:47 -0700374 self._ec_programmer.program()