blob: 29d9a83f9210d05152d0115b8aed48797ceb2311 [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
25class ProgrammerError(Exception):
26 """Local exception class wrapper."""
27 pass
28
29
30class _BaseProgrammer(object):
31 """Class implementing base programmer services.
32
33 Private attributes:
34 _servo: a servo object controlling the servo device
35 _servo_prog_state: a tuple of strings of "<control>:<value>" pairs,
36 listing servo controls and their required values for
37 programming
38 _servo_saved_state: a list of the same elements as _servo_prog_state,
39 those which need to be restored after programming
40 _program_cmd: a string, the shell command to run on the servo host
41 to actually program the firmware. Dependent on
42 firmware/hardware type, set by subclasses.
43 """
44
45 def __init__(self, servo, req_list):
46 """Base constructor.
47 @param servo: a servo object controlling the servo device
48 @param req_list: a list of strings, names of the utilities required
49 to be in the path for the programmer to succeed
50 """
51 self._servo = servo
52 self._servo_prog_state = ()
53 self._servo_saved_state = []
54 self._program_cmd = ''
Yusuf Mohsinally8c4631d2014-05-05 18:20:54 -070055 try:
56 self._servo.system('which %s' % ' '.join(req_list))
57 except error.AutoservRunError:
58 # TODO: We turn this exception into a warn since the fw programmer
59 # is not working right now, and some systems do not package the
60 # required utilities its checking for.
61 # We should reinstate this exception once the programmer is working
62 # to indicate the missing utilities earlier in the test cycle.
63 # Bug chromium:371011 filed to track this.
64 logging.warn("Ignoring exception when verify required bins : %s",
65 ' '.join(req_list))
Yusuf Mohsinally6c078ae2013-11-21 11:06:42 -080066
Dan Shia5fef052015-05-18 23:28:47 -070067
Yusuf Mohsinally6c078ae2013-11-21 11:06:42 -080068 def _set_servo_state(self):
69 """Set servo for programming, while saving the current state."""
70 logging.debug("Setting servo state for programming")
71 for item in self._servo_prog_state:
72 key, value = item.split(':')
73 present = self._servo.get(key)
74 if present != value:
75 self._servo_saved_state.append('%s:%s' % (key, present))
76 self._servo.set(key, value)
77
78
79 def _restore_servo_state(self):
80 """Restore previously saved servo state."""
81 logging.debug("Restoring servo state after programming")
82 self._servo_saved_state.reverse() # Do it in the reverse order.
83 for item in self._servo_saved_state:
84 key, value = item.split(':')
85 self._servo.set(key, value)
86
87
88 def program(self):
89 """Program the firmware as configured by a subclass."""
90 self._set_servo_state()
91 try:
92 logging.debug("Programmer command: %s", self._program_cmd)
93 self._servo.system(self._program_cmd)
94 finally:
95 self._restore_servo_state()
96
97
98class FlashromProgrammer(_BaseProgrammer):
99 """Class for programming AP flashrom."""
100
101 def __init__(self, servo):
102 """Configure required servo state."""
103 super(FlashromProgrammer, self).__init__(servo, ['flashrom',])
104 self._fw_path = None
105 self._tmp_path = '/tmp'
106 self._fw_main = os.path.join(self._tmp_path, 'fw_main')
107 self._ro_vpd = os.path.join(self._tmp_path, 'ro_vpd')
108 self._rw_vpd = os.path.join(self._tmp_path, 'rw_vpd')
109 self._gbb = os.path.join(self._tmp_path, 'gbb')
110
111
112 def program(self):
113 """Program the firmware but preserve VPD and HWID."""
114 assert self._fw_path is not None
115 self._set_servo_state()
116 try:
117 vpd_sections = [('RW_VPD', self._rw_vpd), ('RO_VPD', self._ro_vpd)]
118 gbb_section = [('GBB', self._gbb)]
Dan Shia5fef052015-05-18 23:28:47 -0700119 servo_version = self._servo.get_servo_version()
120 servo_v2_programmer = 'ft2232_spi:type=servo-v2'
121 servo_v3_programmer = 'linux_spi'
122 if servo_version == 'servo_v2':
123 programmer = servo_v2_programmer
124 if self._servo.servo_serial:
125 programmer += ',serial=%s' % self._servo.servo_serial
126 elif servo_version == 'servo_v3':
127 programmer = servo_v3_programmer
128 else:
129 raise Exception('Servo version %s is not supported.' %
130 servo_version)
Yusuf Mohsinally6c078ae2013-11-21 11:06:42 -0800131 # Save needed sections from current firmware
132 for section in vpd_sections + gbb_section:
133 self._servo.system(' '.join([
Dan Shia5fef052015-05-18 23:28:47 -0700134 'flashrom', '-V', '-p', programmer,
Yusuf Mohsinally6c078ae2013-11-21 11:06:42 -0800135 '-r', self._fw_main, '-i', '%s:%s' % section]))
136
137 # Pack the saved VPD into new firmware
138 self._servo.system('cp %s %s' % (self._fw_path, self._fw_main))
139 img_size = self._servo.system_output(
140 "stat -c '%%s' %s" % self._fw_main)
141 pack_cmd = ['flashrom',
142 '-p', 'dummy:image=%s,size=%s,emulate=VARIABLE_SIZE' % (
143 self._fw_main, img_size),
144 '-w', self._fw_main]
145 for section in vpd_sections:
146 pack_cmd.extend(['-i', '%s:%s' % section])
147 self._servo.system(' '.join(pack_cmd))
148
149 # Read original HWID. The output format is:
150 # hardware_id: RAMBI TEST A_A 0128
151 gbb_hwid_output = self._servo.system_output(
152 'gbb_utility -g --hwid %s' % self._gbb)
153 original_hwid = gbb_hwid_output.split(':', 1)[1].strip()
154
155 # Write HWID to new firmware
156 self._servo.system("gbb_utility -s --hwid='%s' %s" %
157 (original_hwid, self._fw_main))
158
159 # Flash the new firmware
160 self._servo.system(' '.join([
Dan Shia5fef052015-05-18 23:28:47 -0700161 'flashrom', '-V', '-p', programmer,
162 '-w', self._fw_main]))
Yusuf Mohsinally6c078ae2013-11-21 11:06:42 -0800163 finally:
164 self._restore_servo_state()
165
166
167 def prepare_programmer(self, path):
168 """Prepare programmer for programming.
169
170 @param path: a string, name of the file containing the firmware image.
171 @param board: a string, used to find servo voltage setting.
172 """
173 faft_config = FAFTConfig(self._servo.get_board())
174 self._fw_path = path
175 self._servo_prog_state = (
Vic Yang15c2dcb2014-06-02 10:31:54 -0700176 'spi2_vref:%s' % faft_config.spi_voltage,
Yusuf Mohsinally6c078ae2013-11-21 11:06:42 -0800177 'spi2_buf_en:on',
178 'spi2_buf_on_flex_en:on',
179 'spi_hold:off',
180 'cold_reset:on',
181 )
182
183
184class FlashECProgrammer(_BaseProgrammer):
185 """Class for programming AP flashrom."""
186
187 def __init__(self, servo):
188 """Configure required servo state."""
189 super(FlashECProgrammer, self).__init__(servo, ['flash_ec',])
190 self._servo = servo
191
192
Dan Shia5fef052015-05-18 23:28:47 -0700193 def prepare_programmer(self, image, board=None):
Yusuf Mohsinally6c078ae2013-11-21 11:06:42 -0800194 """Prepare programmer for programming.
195
196 @param image: string with the location of the image file
Dan Shia5fef052015-05-18 23:28:47 -0700197 @param board: Name of the board used in EC image. Some board's name used
198 by servod might be different from EC. Default to None.
Yusuf Mohsinally6c078ae2013-11-21 11:06:42 -0800199 """
200 # TODO: need to not have port be hardcoded
Dan Shia5fef052015-05-18 23:28:47 -0700201 self._program_cmd = ('flash_ec --board=%s --image=%s --port=%s' %
202 (board or self._servo.get_board(), image, '9999'))
Yusuf Mohsinally6c078ae2013-11-21 11:06:42 -0800203
204
205class ProgrammerV2(object):
Dan Shia5fef052015-05-18 23:28:47 -0700206 """Main programmer class which provides programmer for BIOS and EC with
207 servo V2."""
Yusuf Mohsinally6c078ae2013-11-21 11:06:42 -0800208
209 def __init__(self, servo):
210 self._servo = servo
Ricky Liangc101a562014-05-15 10:56:15 +0800211 self._valid_boards = self._get_valid_v2_boards()
Yusuf Mohsinally6c078ae2013-11-21 11:06:42 -0800212 self._bios_programmer = self._factory_bios(self._servo)
213 self._ec_programmer = self._factory_ec(self._servo)
214
215
Ricky Liangc101a562014-05-15 10:56:15 +0800216 @staticmethod
217 def _get_valid_v2_boards():
218 """Greps servod config files to look for valid v2 boards.
219
220 @return A list of valid board names.
221 """
Ricky Liangc7cc3402014-05-24 00:25:26 +0800222 site_packages_paths = site.getsitepackages()
223 SERVOD_CONFIG_DATA_DIR = None
224 for p in site_packages_paths:
225 servo_data_path = os.path.join(p, 'servo', 'data')
226 if os.path.exists(servo_data_path):
227 SERVOD_CONFIG_DATA_DIR = servo_data_path
228 break
229 if not SERVOD_CONFIG_DATA_DIR:
230 raise ProgrammerError(
231 'Unable to locate data directory of Python servo module')
Ricky Liangc101a562014-05-15 10:56:15 +0800232 SERVOFLEX_V2_R0_P50_CONFIG = 'servoflex_v2_r0_p50.xml'
Ricky Liangf19ab9f2014-06-11 15:50:44 +0800233 SERVO_CONFIG_GLOB = 'servo_*_overlay.xml'
Ricky Liangc101a562014-05-15 10:56:15 +0800234 SERVO_CONFIG_REGEXP = 'servo_(?P<board>.+)_overlay.xml'
235
Ricky Liangf19ab9f2014-06-11 15:50:44 +0800236 def is_v2_compatible_board(board_config_path):
237 """Check if the given board config file is v2-compatible.
238
239 @param board_config_path: Path to a board config XML file.
240
241 @return True if the board is v2-compatible; False otherwise.
242 """
243 configs = []
244 def get_all_includes(config_path):
245 """Get all included XML config names in the given config file.
246
247 @param config_path: Path to a servo config file.
248 """
249 root = xml.etree.ElementTree.parse(config_path).getroot()
250 for element in root.findall('include'):
251 include_name = element.find('name').text
252 configs.append(include_name)
253 get_all_includes(os.path.join(
254 SERVOD_CONFIG_DATA_DIR, include_name))
255
256 get_all_includes(board_config_path)
257 return True if SERVOFLEX_V2_R0_P50_CONFIG in configs else False
258
Ricky Liangc101a562014-05-15 10:56:15 +0800259 result = []
Ricky Liangf19ab9f2014-06-11 15:50:44 +0800260 board_overlays = glob.glob(
261 os.path.join(SERVOD_CONFIG_DATA_DIR, SERVO_CONFIG_GLOB))
262 for overlay_path in board_overlays:
263 if is_v2_compatible_board(overlay_path):
264 result.append(re.search(SERVO_CONFIG_REGEXP,
265 overlay_path).group('board'))
Ricky Liangc101a562014-05-15 10:56:15 +0800266 return result
267
268
Yusuf Mohsinally6c078ae2013-11-21 11:06:42 -0800269 def _factory_bios(self, servo):
270 """Instantiates and returns (bios, ec) programmers for the board.
271
272 @param servo: A servo object.
273
274 @return A programmer for ec. If the programmer is not supported
275 for the board, None will be returned.
276 """
277 _bios_prog = None
278 _board = servo.get_board()
279
280 servo_prog_state = [
281 'spi2_buf_en:on',
282 'spi2_buf_on_flex_en:on',
283 'spi_hold:off',
284 'cold_reset:on',
285 ]
286
287 logging.debug('Setting up BIOS programmer for board: %s', _board)
Ricky Liangc101a562014-05-15 10:56:15 +0800288 if _board in self._valid_boards:
Yusuf Mohsinally6c078ae2013-11-21 11:06:42 -0800289 _bios_prog = FlashromProgrammer(servo)
290 else:
Ilja H. Friedel04be2bd2014-05-07 21:29:59 -0700291 logging.warning('No BIOS programmer found for board: %s', _board)
Yusuf Mohsinally6c078ae2013-11-21 11:06:42 -0800292
293 return _bios_prog
294
295
296 def _factory_ec(self, servo):
297 """Instantiates and returns ec programmer for the board.
298
299 @param servo: A servo object.
300
301 @return A programmer for ec. If the programmer is not supported
302 for the board, None will be returned.
303 """
304 _ec_prog = None
305 _board = servo.get_board()
306
307 logging.debug('Setting up EC programmer for board: %s', _board)
Ricky Liangc101a562014-05-15 10:56:15 +0800308 if _board in self._valid_boards:
Yusuf Mohsinally6c078ae2013-11-21 11:06:42 -0800309 _ec_prog = FlashECProgrammer(servo)
310 else:
Ilja H. Friedel04be2bd2014-05-07 21:29:59 -0700311 logging.warning('No EC programmer found for board: %s', _board)
Yusuf Mohsinally6c078ae2013-11-21 11:06:42 -0800312
313 return _ec_prog
314
315
316 def program_bios(self, image):
317 """Programs the DUT with provide bios image.
318
319 @param image: (required) location of bios image file.
320
321 """
322 self._bios_programmer.prepare_programmer(image)
323 self._bios_programmer.program()
324
Dan Shia5fef052015-05-18 23:28:47 -0700325
Yusuf Mohsinally6c078ae2013-11-21 11:06:42 -0800326 def program_ec(self, image):
327 """Programs the DUT with provide ec image.
328
329 @param image: (required) location of ec image file.
330
331 """
332 self._ec_programmer.prepare_programmer(image)
333 self._ec_programmer.program()
Dan Shia5fef052015-05-18 23:28:47 -0700334
335
336class ProgrammerV3(object):
337 """Main programmer class which provides programmer for BIOS and EC with
338 servo V3.
339
340 Different from programmer for servo v2, programmer for servo v3 does not
341 try to validate if the board can use servo V3 to update firmware. As long as
342 the servod process running in beagblebone with given board, the program will
343 attempt to flash bios and ec.
344
345 """
346
347 def __init__(self, servo):
348 self._servo = servo
349 self._bios_programmer = FlashromProgrammer(servo)
350 self._ec_programmer = FlashECProgrammer(servo)
351
352
353 def program_bios(self, image):
354 """Programs the DUT with provide bios image.
355
356 @param image: (required) location of bios image file.
357
358 """
359 self._bios_programmer.prepare_programmer(image)
360 self._bios_programmer.program()
361
362
363 def program_ec(self, image, board=None):
364 """Programs the DUT with provide ec image.
365
366 @param image: (required) location of ec image file.
367 @param board: Name of the board used in EC image. Some board's name used
368 by servod might be different from EC. Default to None.
369
370 """
371 self._ec_programmer.prepare_programmer(image, board)
372 self._ec_programmer.program()