• Home
  • Line#
  • Scopes#
  • Navigate#
  • Raw
  • Download
1# 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
14import glob
15import logging
16import os
17import re
18import site
19import time
20import xml.etree.ElementTree
21
22from autotest_lib.client.common_lib import error
23from autotest_lib.server.cros.faft.config.config import Config as FAFTConfig
24
25
26# Number of seconds for program EC/BIOS to time out.
27FIRMWARE_PROGRAM_TIMEOUT_SEC = 600
28
29class 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
42      _servo_prog_state_delay: time in second to wait after changing servo
43                               controls for programming.
44      _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 = ()
59        self._servo_prog_state_delay = 0
60        self._servo_saved_state = []
61        self._program_cmd = ''
62        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))
73
74
75    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(':')
80            try:
81                present = self._servo.get(key)
82            except error.TestFail:
83                logging.warn('Missing servo control: %s', key)
84                continue
85            if present != value:
86                self._servo_saved_state.append('%s:%s' % (key, present))
87            self._servo.set(key, value)
88        time.sleep(self._servo_prog_state_delay)
89
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)
105            self._servo.system(self._program_cmd,
106                               timeout=FIRMWARE_PROGRAM_TIMEOUT_SEC)
107        finally:
108            self._restore_servo_state()
109
110
111class FlashromProgrammer(_BaseProgrammer):
112    """Class for programming AP flashrom."""
113
114    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        """
120        super(FlashromProgrammer, self).__init__(servo, ['flashrom',])
121        self._keep_ro = keep_ro
122        self._fw_path = None
123        self._tmp_path = '/tmp'
124        self._fw_main = os.path.join(self._tmp_path, 'fw_main')
125        self._wp_ro = os.path.join(self._tmp_path, 'wp_ro')
126        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:
136            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)]
139            gbb_section = [('GBB', self._gbb)]
140            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
146            servo_version = self._servo.get_servo_version()
147            servo_v2_programmer = 'ft2232_spi:type=servo-v2'
148            servo_v3_programmer = 'linux_spi'
149            if servo_version == 'servo_v2':
150                programmer = servo_v2_programmer
151                if self._servo.servo_serial:
152                    programmer += ',serial=%s' % self._servo.servo_serial
153            elif servo_version == 'servo_v3':
154                programmer = servo_v3_programmer
155            else:
156                raise Exception('Servo version %s is not supported.' %
157                                servo_version)
158            # Save needed sections from current firmware
159            for section in preserved_sections + gbb_section:
160                self._servo.system(' '.join([
161                    'flashrom', '-V', '-p', programmer,
162                    '-r', self._fw_main, '-i', '%s:%s' % section]),
163                    timeout=FIRMWARE_PROGRAM_TIMEOUT_SEC)
164
165            # Pack the saved VPD into new firmware
166            self._servo.system('cp %s %s' % (self._fw_path, self._fw_main))
167            img_size = self._servo.system_output(
168                    "stat -c '%%s' %s" % self._fw_main)
169            pack_cmd = ['flashrom',
170                    '-p', 'dummy:image=%s,size=%s,emulate=VARIABLE_SIZE' % (
171                        self._fw_main, img_size),
172                    '-w', self._fw_main]
173            for section in preserved_sections:
174                pack_cmd.extend(['-i', '%s:%s' % section])
175            self._servo.system(' '.join(pack_cmd),
176                               timeout=FIRMWARE_PROGRAM_TIMEOUT_SEC)
177
178            # HWID is inside the RO portion. Don't preserve HWID if we keep RO.
179            if not self._keep_ro:
180                # Read original HWID. The output format is:
181                #    hardware_id: RAMBI TEST A_A 0128
182                gbb_hwid_output = self._servo.system_output(
183                        'gbb_utility -g --hwid %s' % self._gbb)
184                original_hwid = gbb_hwid_output.split(':', 1)[1].strip()
185
186                # Write HWID to new firmware
187                self._servo.system("gbb_utility -s --hwid='%s' %s" %
188                        (original_hwid, self._fw_main))
189
190            # Flash the new firmware
191            self._servo.system(' '.join([
192                    'flashrom', '-V', '-p', programmer,
193                    '-w', self._fw_main]), timeout=FIRMWARE_PROGRAM_TIMEOUT_SEC)
194        finally:
195            self._restore_servo_state()
196
197
198    def prepare_programmer(self, path):
199        """Prepare programmer for programming.
200
201        @param path: a string, name of the file containing the firmware image.
202        @param board: a string, used to find servo voltage setting.
203        """
204        faft_config = FAFTConfig(self._servo.get_board())
205        self._fw_path = path
206        self._servo_prog_state_delay = faft_config.servo_prog_state_delay
207        self._servo_prog_state = (
208            'spi2_vref:%s' % faft_config.spi_voltage,
209            'spi2_buf_en:on',
210            'spi2_buf_on_flex_en:on',
211            'spi_hold:off',
212            'cold_reset:on',
213            'usbpd_reset:on',
214            )
215
216
217class FlashECProgrammer(_BaseProgrammer):
218    """Class for programming AP flashrom."""
219
220    def __init__(self, servo):
221        """Configure required servo state."""
222        super(FlashECProgrammer, self).__init__(servo, ['flash_ec',])
223        self._servo = servo
224
225
226    def prepare_programmer(self, image):
227        """Prepare programmer for programming.
228
229        @param image: string with the location of the image file
230        """
231        # TODO: need to not have port be hardcoded
232        self._program_cmd = ('flash_ec --chip=%s --image=%s --port=%s' %
233                             (self._servo.get('ec_chip'), image, '9999'))
234
235
236class ProgrammerV2(object):
237    """Main programmer class which provides programmer for BIOS and EC with
238    servo V2."""
239
240    def __init__(self, servo):
241        self._servo = servo
242        self._valid_boards = self._get_valid_v2_boards()
243        self._bios_programmer = self._factory_bios(self._servo)
244        self._ec_programmer = self._factory_ec(self._servo)
245
246
247    @staticmethod
248    def _get_valid_v2_boards():
249        """Greps servod config files to look for valid v2 boards.
250
251        @return A list of valid board names.
252        """
253        site_packages_paths = site.getsitepackages()
254        SERVOD_CONFIG_DATA_DIR = None
255        for p in site_packages_paths:
256            servo_data_path = os.path.join(p, 'servo', 'data')
257            if os.path.exists(servo_data_path):
258                SERVOD_CONFIG_DATA_DIR = servo_data_path
259                break
260        if not SERVOD_CONFIG_DATA_DIR:
261            raise ProgrammerError(
262                    'Unable to locate data directory of Python servo module')
263        SERVOFLEX_V2_R0_P50_CONFIG = 'servoflex_v2_r0_p50.xml'
264        SERVO_CONFIG_GLOB = 'servo_*_overlay.xml'
265        SERVO_CONFIG_REGEXP = 'servo_(?P<board>.+)_overlay.xml'
266
267        def is_v2_compatible_board(board_config_path):
268            """Check if the given board config file is v2-compatible.
269
270            @param board_config_path: Path to a board config XML file.
271
272            @return True if the board is v2-compatible; False otherwise.
273            """
274            configs = []
275            def get_all_includes(config_path):
276                """Get all included XML config names in the given config file.
277
278                @param config_path: Path to a servo config file.
279                """
280                root = xml.etree.ElementTree.parse(config_path).getroot()
281                for element in root.findall('include'):
282                    include_name = element.find('name').text
283                    configs.append(include_name)
284                    get_all_includes(os.path.join(
285                            SERVOD_CONFIG_DATA_DIR, include_name))
286
287            get_all_includes(board_config_path)
288            return True if SERVOFLEX_V2_R0_P50_CONFIG in configs else False
289
290        result = []
291        board_overlays = glob.glob(
292                os.path.join(SERVOD_CONFIG_DATA_DIR, SERVO_CONFIG_GLOB))
293        for overlay_path in board_overlays:
294            if is_v2_compatible_board(overlay_path):
295                result.append(re.search(SERVO_CONFIG_REGEXP,
296                                        overlay_path).group('board'))
297        return result
298
299
300    def _factory_bios(self, servo):
301        """Instantiates and returns (bios, ec) programmers 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        _bios_prog = None
309        _board = servo.get_board()
310
311        servo_prog_state = [
312            'spi2_buf_en:on',
313            'spi2_buf_on_flex_en:on',
314            'spi_hold:off',
315            'cold_reset:on',
316            ]
317
318        logging.debug('Setting up BIOS programmer for board: %s', _board)
319        if _board in self._valid_boards:
320            _bios_prog = FlashromProgrammer(servo)
321        else:
322            logging.warning('No BIOS programmer found for board: %s', _board)
323
324        return _bios_prog
325
326
327    def _factory_ec(self, servo):
328        """Instantiates and returns ec programmer for the board.
329
330        @param servo: A servo object.
331
332        @return A programmer for ec. If the programmer is not supported
333            for the board, None will be returned.
334        """
335        _ec_prog = None
336        _board = servo.get_board()
337
338        logging.debug('Setting up EC programmer for board: %s', _board)
339        if _board in self._valid_boards:
340            _ec_prog = FlashECProgrammer(servo)
341        else:
342            logging.warning('No EC programmer found for board: %s', _board)
343
344        return _ec_prog
345
346
347    def program_bios(self, image):
348        """Programs the DUT with provide bios image.
349
350        @param image: (required) location of bios image file.
351
352        """
353        self._bios_programmer.prepare_programmer(image)
354        self._bios_programmer.program()
355
356
357    def program_ec(self, image):
358        """Programs the DUT with provide ec image.
359
360        @param image: (required) location of ec image file.
361
362        """
363        self._ec_programmer.prepare_programmer(image)
364        self._ec_programmer.program()
365
366
367class ProgrammerV3(object):
368    """Main programmer class which provides programmer for BIOS and EC with
369    servo V3.
370
371    Different from programmer for servo v2, programmer for servo v3 does not
372    try to validate if the board can use servo V3 to update firmware. As long as
373    the servod process running in beagblebone with given board, the program will
374    attempt to flash bios and ec.
375
376    """
377
378    def __init__(self, servo):
379        self._servo = servo
380        self._bios_programmer = FlashromProgrammer(servo)
381        self._ec_programmer = FlashECProgrammer(servo)
382
383
384    def program_bios(self, image):
385        """Programs the DUT with provide bios image.
386
387        @param image: (required) location of bios image file.
388
389        """
390        self._bios_programmer.prepare_programmer(image)
391        self._bios_programmer.program()
392
393
394    def program_ec(self, image):
395        """Programs the DUT with provide ec image.
396
397        @param image: (required) location of ec image file.
398
399        """
400        self._ec_programmer.prepare_programmer(image)
401        self._ec_programmer.program()
402
403
404class ProgrammerV3RwOnly(object):
405    """Main programmer class which provides programmer for only updating the RW
406    portion of BIOS with servo V3.
407
408    It does nothing on EC, as EC software sync on the next boot will
409    automatically overwrite the EC RW portion, using the EC RW image inside
410    the BIOS RW image.
411
412    """
413
414    def __init__(self, servo):
415        self._servo = servo
416        self._bios_programmer = FlashromProgrammer(servo, keep_ro=True)
417
418
419    def program_bios(self, image):
420        """Programs the DUT with provide bios image.
421
422        @param image: (required) location of bios image file.
423
424        """
425        self._bios_programmer.prepare_programmer(image)
426        self._bios_programmer.program()
427
428
429    def program_ec(self, image):
430        """Programs the DUT with provide ec image.
431
432        @param image: (required) location of ec image file.
433
434        """
435        # Do nothing. EC software sync will update the EC RW.
436        pass
437