blob: 85f6ea5acc88106a0f7fa7f6e641c0061c50c0c8 [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
67 def _set_servo_state(self):
68 """Set servo for programming, while saving the current state."""
69 logging.debug("Setting servo state for programming")
70 for item in self._servo_prog_state:
71 key, value = item.split(':')
72 present = self._servo.get(key)
73 if present != value:
74 self._servo_saved_state.append('%s:%s' % (key, present))
75 self._servo.set(key, value)
76
77
78 def _restore_servo_state(self):
79 """Restore previously saved servo state."""
80 logging.debug("Restoring servo state after programming")
81 self._servo_saved_state.reverse() # Do it in the reverse order.
82 for item in self._servo_saved_state:
83 key, value = item.split(':')
84 self._servo.set(key, value)
85
86
87 def program(self):
88 """Program the firmware as configured by a subclass."""
89 self._set_servo_state()
90 try:
91 logging.debug("Programmer command: %s", self._program_cmd)
92 self._servo.system(self._program_cmd)
93 finally:
94 self._restore_servo_state()
95
96
97class FlashromProgrammer(_BaseProgrammer):
98 """Class for programming AP flashrom."""
99
100 def __init__(self, servo):
101 """Configure required servo state."""
102 super(FlashromProgrammer, self).__init__(servo, ['flashrom',])
103 self._fw_path = None
104 self._tmp_path = '/tmp'
105 self._fw_main = os.path.join(self._tmp_path, 'fw_main')
106 self._ro_vpd = os.path.join(self._tmp_path, 'ro_vpd')
107 self._rw_vpd = os.path.join(self._tmp_path, 'rw_vpd')
108 self._gbb = os.path.join(self._tmp_path, 'gbb')
109
110
111 def program(self):
112 """Program the firmware but preserve VPD and HWID."""
113 assert self._fw_path is not None
114 self._set_servo_state()
115 try:
116 vpd_sections = [('RW_VPD', self._rw_vpd), ('RO_VPD', self._ro_vpd)]
117 gbb_section = [('GBB', self._gbb)]
Ricky Liang0dd379c2014-04-23 16:29:08 +0800118 ft2232_programmer = 'ft2232_spi:type=servo-v2'
119 if self._servo.servo_serial:
120 ft2232_programmer += ',serial=%s' % self._servo.servo_serial
Yusuf Mohsinally6c078ae2013-11-21 11:06:42 -0800121
122 # Save needed sections from current firmware
123 for section in vpd_sections + gbb_section:
124 self._servo.system(' '.join([
Ricky Liang0dd379c2014-04-23 16:29:08 +0800125 'flashrom', '-V', '-p', ft2232_programmer,
Yusuf Mohsinally6c078ae2013-11-21 11:06:42 -0800126 '-r', self._fw_main, '-i', '%s:%s' % section]))
127
128 # Pack the saved VPD into new firmware
129 self._servo.system('cp %s %s' % (self._fw_path, self._fw_main))
130 img_size = self._servo.system_output(
131 "stat -c '%%s' %s" % self._fw_main)
132 pack_cmd = ['flashrom',
133 '-p', 'dummy:image=%s,size=%s,emulate=VARIABLE_SIZE' % (
134 self._fw_main, img_size),
135 '-w', self._fw_main]
136 for section in vpd_sections:
137 pack_cmd.extend(['-i', '%s:%s' % section])
138 self._servo.system(' '.join(pack_cmd))
139
140 # Read original HWID. The output format is:
141 # hardware_id: RAMBI TEST A_A 0128
142 gbb_hwid_output = self._servo.system_output(
143 'gbb_utility -g --hwid %s' % self._gbb)
144 original_hwid = gbb_hwid_output.split(':', 1)[1].strip()
145
146 # Write HWID to new firmware
147 self._servo.system("gbb_utility -s --hwid='%s' %s" %
148 (original_hwid, self._fw_main))
149
150 # Flash the new firmware
151 self._servo.system(' '.join([
Ricky Liang0dd379c2014-04-23 16:29:08 +0800152 'flashrom', '-V', '-p', ft2232_programmer,
Yusuf Mohsinally6c078ae2013-11-21 11:06:42 -0800153 '-w', self._fw_main]))
154 finally:
155 self._restore_servo_state()
156
157
158 def prepare_programmer(self, path):
159 """Prepare programmer for programming.
160
161 @param path: a string, name of the file containing the firmware image.
162 @param board: a string, used to find servo voltage setting.
163 """
164 faft_config = FAFTConfig(self._servo.get_board())
165 self._fw_path = path
166 self._servo_prog_state = (
Vic Yang15c2dcb2014-06-02 10:31:54 -0700167 'spi2_vref:%s' % faft_config.spi_voltage,
Yusuf Mohsinally6c078ae2013-11-21 11:06:42 -0800168 'spi2_buf_en:on',
169 'spi2_buf_on_flex_en:on',
170 'spi_hold:off',
171 'cold_reset:on',
172 )
173
174
175class FlashECProgrammer(_BaseProgrammer):
176 """Class for programming AP flashrom."""
177
178 def __init__(self, servo):
179 """Configure required servo state."""
180 super(FlashECProgrammer, self).__init__(servo, ['flash_ec',])
181 self._servo = servo
182
183
184 def prepare_programmer(self, image):
185 """Prepare programmer for programming.
186
187 @param image: string with the location of the image file
188 """
189 # TODO: need to not have port be hardcoded
190 self._program_cmd = 'flash_ec --board=%s --image=%s --port=%s' % (
191 self._servo.get_board(), image, '9999')
192
193
194class ProgrammerV2(object):
195 """Main programmer class which provides programmer for BIOS and EC."""
196
197 def __init__(self, servo):
198 self._servo = servo
Ricky Liangc101a562014-05-15 10:56:15 +0800199 self._valid_boards = self._get_valid_v2_boards()
Yusuf Mohsinally6c078ae2013-11-21 11:06:42 -0800200 self._bios_programmer = self._factory_bios(self._servo)
201 self._ec_programmer = self._factory_ec(self._servo)
202
203
Ricky Liangc101a562014-05-15 10:56:15 +0800204 @staticmethod
205 def _get_valid_v2_boards():
206 """Greps servod config files to look for valid v2 boards.
207
208 @return A list of valid board names.
209 """
Ricky Liangc7cc3402014-05-24 00:25:26 +0800210 site_packages_paths = site.getsitepackages()
211 SERVOD_CONFIG_DATA_DIR = None
212 for p in site_packages_paths:
213 servo_data_path = os.path.join(p, 'servo', 'data')
214 if os.path.exists(servo_data_path):
215 SERVOD_CONFIG_DATA_DIR = servo_data_path
216 break
217 if not SERVOD_CONFIG_DATA_DIR:
218 raise ProgrammerError(
219 'Unable to locate data directory of Python servo module')
Ricky Liangc101a562014-05-15 10:56:15 +0800220 SERVOFLEX_V2_R0_P50_CONFIG = 'servoflex_v2_r0_p50.xml'
Ricky Liangf19ab9f2014-06-11 15:50:44 +0800221 SERVO_CONFIG_GLOB = 'servo_*_overlay.xml'
Ricky Liangc101a562014-05-15 10:56:15 +0800222 SERVO_CONFIG_REGEXP = 'servo_(?P<board>.+)_overlay.xml'
223
Ricky Liangf19ab9f2014-06-11 15:50:44 +0800224 def is_v2_compatible_board(board_config_path):
225 """Check if the given board config file is v2-compatible.
226
227 @param board_config_path: Path to a board config XML file.
228
229 @return True if the board is v2-compatible; False otherwise.
230 """
231 configs = []
232 def get_all_includes(config_path):
233 """Get all included XML config names in the given config file.
234
235 @param config_path: Path to a servo config file.
236 """
237 root = xml.etree.ElementTree.parse(config_path).getroot()
238 for element in root.findall('include'):
239 include_name = element.find('name').text
240 configs.append(include_name)
241 get_all_includes(os.path.join(
242 SERVOD_CONFIG_DATA_DIR, include_name))
243
244 get_all_includes(board_config_path)
245 return True if SERVOFLEX_V2_R0_P50_CONFIG in configs else False
246
Ricky Liangc101a562014-05-15 10:56:15 +0800247 result = []
Ricky Liangf19ab9f2014-06-11 15:50:44 +0800248 board_overlays = glob.glob(
249 os.path.join(SERVOD_CONFIG_DATA_DIR, SERVO_CONFIG_GLOB))
250 for overlay_path in board_overlays:
251 if is_v2_compatible_board(overlay_path):
252 result.append(re.search(SERVO_CONFIG_REGEXP,
253 overlay_path).group('board'))
Ricky Liangc101a562014-05-15 10:56:15 +0800254 return result
255
256
Yusuf Mohsinally6c078ae2013-11-21 11:06:42 -0800257 def _factory_bios(self, servo):
258 """Instantiates and returns (bios, ec) programmers for the board.
259
260 @param servo: A servo object.
261
262 @return A programmer for ec. If the programmer is not supported
263 for the board, None will be returned.
264 """
265 _bios_prog = None
266 _board = servo.get_board()
267
268 servo_prog_state = [
269 'spi2_buf_en:on',
270 'spi2_buf_on_flex_en:on',
271 'spi_hold:off',
272 'cold_reset:on',
273 ]
274
275 logging.debug('Setting up BIOS programmer for board: %s', _board)
Ricky Liangc101a562014-05-15 10:56:15 +0800276 if _board in self._valid_boards:
Yusuf Mohsinally6c078ae2013-11-21 11:06:42 -0800277 _bios_prog = FlashromProgrammer(servo)
278 else:
Ilja H. Friedel04be2bd2014-05-07 21:29:59 -0700279 logging.warning('No BIOS programmer found for board: %s', _board)
Yusuf Mohsinally6c078ae2013-11-21 11:06:42 -0800280
281 return _bios_prog
282
283
284 def _factory_ec(self, servo):
285 """Instantiates and returns ec programmer 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 _ec_prog = None
293 _board = servo.get_board()
294
295 logging.debug('Setting up EC programmer for board: %s', _board)
Ricky Liangc101a562014-05-15 10:56:15 +0800296 if _board in self._valid_boards:
Yusuf Mohsinally6c078ae2013-11-21 11:06:42 -0800297 _ec_prog = FlashECProgrammer(servo)
298 else:
Ilja H. Friedel04be2bd2014-05-07 21:29:59 -0700299 logging.warning('No EC programmer found for board: %s', _board)
Yusuf Mohsinally6c078ae2013-11-21 11:06:42 -0800300
301 return _ec_prog
302
303
304 def program_bios(self, image):
305 """Programs the DUT with provide bios image.
306
307 @param image: (required) location of bios image file.
308
309 """
310 self._bios_programmer.prepare_programmer(image)
311 self._bios_programmer.program()
312
313 def program_ec(self, image):
314 """Programs the DUT with provide ec image.
315
316 @param image: (required) location of ec image file.
317
318 """
319 self._ec_programmer.prepare_programmer(image)
320 self._ec_programmer.program()