• 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        self._servo_version = self._servo.get_servo_version()
130        self._servo_serials = self._servo._server.get_servo_serials()
131
132
133    def program(self):
134        """Program the firmware but preserve VPD and HWID."""
135        assert self._fw_path is not None
136        self._set_servo_state()
137        try:
138            wp_ro_section = [('WP_RO', self._wp_ro)]
139            rw_vpd_section = [('RW_VPD', self._rw_vpd)]
140            ro_vpd_section = [('RO_VPD', self._ro_vpd)]
141            gbb_section = [('GBB', self._gbb)]
142            if self._keep_ro:
143                # Keep the whole RO portion
144                preserved_sections = wp_ro_section + rw_vpd_section
145            else:
146                preserved_sections = ro_vpd_section + rw_vpd_section
147
148            servo_v2_programmer = 'ft2232_spi:type=servo-v2'
149            servo_v3_programmer = 'linux_spi'
150            servo_v4_with_micro_programmer = 'raiden_spi'
151            servo_v4_with_ccd_programmer = 'raiden_debug_spi:target=AP'
152            if self._servo_version == 'servo_v2':
153                programmer = servo_v2_programmer
154                servo_serial = self._servo_serials.get('main')
155                if servo_serial:
156                    programmer += ',serial=%s' % servo_serial
157            elif self._servo_version == 'servo_v3':
158                programmer = servo_v3_programmer
159            elif self._servo_version == 'servo_v4_with_servo_micro':
160                # When a uServo is connected to a DUT with CCD support, the
161                # firmware programmer will always use the uServo to program.
162                servo_micro_serial = self._servo_serials.get('servo_micro')
163                programmer = servo_v4_with_micro_programmer
164                programmer += ',serial=%s' % servo_micro_serial
165            elif self._servo_version == 'servo_v4_with_ccd_cr50':
166                ccd_serial = self._servo_serials.get('ccd')
167                programmer = servo_v4_with_ccd_programmer
168                programmer += ',serial=%s' % ccd_serial
169            else:
170                raise Exception('Servo version %s is not supported.' %
171                                self._servo_version)
172            # Save needed sections from current firmware
173            for section in preserved_sections + gbb_section:
174                self._servo.system(' '.join([
175                    'flashrom', '-V', '-p', programmer,
176                    '-r', self._fw_main, '-i', '%s:%s' % section]),
177                    timeout=FIRMWARE_PROGRAM_TIMEOUT_SEC)
178
179            # Pack the saved VPD into new firmware
180            self._servo.system('cp %s %s' % (self._fw_path, self._fw_main))
181            img_size = self._servo.system_output(
182                    "stat -c '%%s' %s" % self._fw_main)
183            pack_cmd = ['flashrom',
184                    '-p', 'dummy:image=%s,size=%s,emulate=VARIABLE_SIZE' % (
185                        self._fw_main, img_size),
186                    '-w', self._fw_main]
187            for section in preserved_sections:
188                pack_cmd.extend(['-i', '%s:%s' % section])
189            self._servo.system(' '.join(pack_cmd),
190                               timeout=FIRMWARE_PROGRAM_TIMEOUT_SEC)
191
192            # HWID is inside the RO portion. Don't preserve HWID if we keep RO.
193            if not self._keep_ro:
194                # Read original HWID. The output format is:
195                #    hardware_id: RAMBI TEST A_A 0128
196                gbb_hwid_output = self._servo.system_output(
197                        'gbb_utility -g --hwid %s' % self._gbb)
198                original_hwid = gbb_hwid_output.split(':', 1)[1].strip()
199
200                # Write HWID to new firmware
201                self._servo.system("gbb_utility -s --hwid='%s' %s" %
202                        (original_hwid, self._fw_main))
203
204            # Flash the new firmware
205            self._servo.system(' '.join([
206                    'flashrom', '-V', '-p', programmer,
207                    '-w', self._fw_main]), timeout=FIRMWARE_PROGRAM_TIMEOUT_SEC)
208        finally:
209            self._servo.get_power_state_controller().reset()
210            self._restore_servo_state()
211
212
213    def prepare_programmer(self, path):
214        """Prepare programmer for programming.
215
216        @param path: a string, name of the file containing the firmware image.
217        """
218        self._fw_path = path
219        # CCD takes care holding AP/EC. Don't need the following steps.
220        if self._servo_version != 'servo_v4_with_ccd_cr50':
221            faft_config = FAFTConfig(self._servo.get_board())
222            self._servo_prog_state_delay = faft_config.servo_prog_state_delay
223            self._servo_prog_state = (
224                'spi2_vref:%s' % faft_config.spi_voltage,
225                'spi2_buf_en:on',
226                'spi2_buf_on_flex_en:on',
227                'spi_hold:off',
228                'cold_reset:on',
229                'usbpd_reset:on',
230                )
231
232
233class FlashECProgrammer(_BaseProgrammer):
234    """Class for programming AP flashrom."""
235
236    def __init__(self, servo):
237        """Configure required servo state."""
238        super(FlashECProgrammer, self).__init__(servo, ['flash_ec',])
239        self._servo = servo
240        self._servo_version = self._servo.get_servo_version()
241
242    def prepare_programmer(self, image):
243        """Prepare programmer for programming.
244
245        @param image: string with the location of the image file
246        """
247        port = self._servo._servo_host.servo_port
248        self._program_cmd = ('flash_ec --chip=%s --image=%s --port=%d' %
249                             (self._servo.get('ec_chip'), image, port))
250        if self._servo_version == 'servo_v4_with_ccd_cr50':
251            self._program_cmd += ' --raiden'
252
253
254class ProgrammerV2(object):
255    """Main programmer class which provides programmer for BIOS and EC with
256    servo V2."""
257
258    def __init__(self, servo):
259        self._servo = servo
260        self._valid_boards = self._get_valid_v2_boards()
261        self._bios_programmer = self._factory_bios(self._servo)
262        self._ec_programmer = self._factory_ec(self._servo)
263
264
265    @staticmethod
266    def _get_valid_v2_boards():
267        """Greps servod config files to look for valid v2 boards.
268
269        @return A list of valid board names.
270        """
271        site_packages_paths = site.getsitepackages()
272        SERVOD_CONFIG_DATA_DIR = None
273        for p in site_packages_paths:
274            servo_data_path = os.path.join(p, 'servo', 'data')
275            if os.path.exists(servo_data_path):
276                SERVOD_CONFIG_DATA_DIR = servo_data_path
277                break
278        if not SERVOD_CONFIG_DATA_DIR:
279            raise ProgrammerError(
280                    'Unable to locate data directory of Python servo module')
281        SERVOFLEX_V2_R0_P50_CONFIG = 'servoflex_v2_r0_p50.xml'
282        SERVO_CONFIG_GLOB = 'servo_*_overlay.xml'
283        SERVO_CONFIG_REGEXP = 'servo_(?P<board>.+)_overlay.xml'
284
285        def is_v2_compatible_board(board_config_path):
286            """Check if the given board config file is v2-compatible.
287
288            @param board_config_path: Path to a board config XML file.
289
290            @return True if the board is v2-compatible; False otherwise.
291            """
292            configs = []
293            def get_all_includes(config_path):
294                """Get all included XML config names in the given config file.
295
296                @param config_path: Path to a servo config file.
297                """
298                root = xml.etree.ElementTree.parse(config_path).getroot()
299                for element in root.findall('include'):
300                    include_name = element.find('name').text
301                    configs.append(include_name)
302                    get_all_includes(os.path.join(
303                            SERVOD_CONFIG_DATA_DIR, include_name))
304
305            get_all_includes(board_config_path)
306            return True if SERVOFLEX_V2_R0_P50_CONFIG in configs else False
307
308        result = []
309        board_overlays = glob.glob(
310                os.path.join(SERVOD_CONFIG_DATA_DIR, SERVO_CONFIG_GLOB))
311        for overlay_path in board_overlays:
312            if is_v2_compatible_board(overlay_path):
313                result.append(re.search(SERVO_CONFIG_REGEXP,
314                                        overlay_path).group('board'))
315        return result
316
317
318    def _get_flashrom_programmer(self, servo):
319        """Gets a proper flashrom programmer.
320
321        @param servo: A servo object.
322
323        @return A programmer for flashrom.
324        """
325        return FlashromProgrammer(servo)
326
327
328    def _factory_bios(self, servo):
329        """Instantiates and returns (bios, ec) programmers for the board.
330
331        @param servo: A servo object.
332
333        @return A programmer for ec. If the programmer is not supported
334            for the board, None will be returned.
335        """
336        _bios_prog = None
337        _board = servo.get_board()
338
339        logging.debug('Setting up BIOS programmer for board: %s', _board)
340        if _board in self._valid_boards:
341            _bios_prog = self._get_flashrom_programmer(servo)
342        else:
343            logging.warning('No BIOS programmer found for board: %s', _board)
344
345        return _bios_prog
346
347
348    def _factory_ec(self, servo):
349        """Instantiates and returns ec programmer for the board.
350
351        @param servo: A servo object.
352
353        @return A programmer for ec. If the programmer is not supported
354            for the board, None will be returned.
355        """
356        _ec_prog = None
357        _board = servo.get_board()
358
359        logging.debug('Setting up EC programmer for board: %s', _board)
360        if _board in self._valid_boards:
361            _ec_prog = FlashECProgrammer(servo)
362        else:
363            logging.warning('No EC programmer found for board: %s', _board)
364
365        return _ec_prog
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
378    def program_ec(self, image):
379        """Programs the DUT with provide ec image.
380
381        @param image: (required) location of ec image file.
382
383        """
384        self._ec_programmer.prepare_programmer(image)
385        self._ec_programmer.program()
386
387
388class ProgrammerV2RwOnly(ProgrammerV2):
389    """Main programmer class which provides programmer for only updating the RW
390    portion of BIOS with servo V2.
391
392    It does nothing on EC, as EC software sync on the next boot will
393    automatically overwrite the EC RW portion, using the EC RW image inside
394    the BIOS RW image.
395
396    """
397
398    def _get_flashrom_programmer(self, servo):
399        """Gets a proper flashrom programmer.
400
401        @param servo: A servo object.
402
403        @return A programmer for flashrom.
404        """
405        return FlashromProgrammer(servo, keep_ro=True)
406
407
408    def program_ec(self, image):
409        """Programs the DUT with provide ec image.
410
411        @param image: (required) location of ec image file.
412
413        """
414        # Do nothing. EC software sync will update the EC RW.
415        pass
416
417
418class ProgrammerV3(object):
419    """Main programmer class which provides programmer for BIOS and EC with
420    servo V3.
421
422    Different from programmer for servo v2, programmer for servo v3 does not
423    try to validate if the board can use servo V3 to update firmware. As long as
424    the servod process running in beagblebone with given board, the program will
425    attempt to flash bios and ec.
426
427    """
428
429    def __init__(self, servo):
430        self._servo = servo
431        self._bios_programmer = FlashromProgrammer(servo)
432        self._ec_programmer = FlashECProgrammer(servo)
433
434
435    def program_bios(self, image):
436        """Programs the DUT with provide bios image.
437
438        @param image: (required) location of bios image file.
439
440        """
441        self._bios_programmer.prepare_programmer(image)
442        self._bios_programmer.program()
443
444
445    def program_ec(self, image):
446        """Programs the DUT with provide ec image.
447
448        @param image: (required) location of ec image file.
449
450        """
451        self._ec_programmer.prepare_programmer(image)
452        self._ec_programmer.program()
453
454
455class ProgrammerV3RwOnly(ProgrammerV3):
456    """Main programmer class which provides programmer for only updating the RW
457    portion of BIOS with servo V3.
458
459    It does nothing on EC, as EC software sync on the next boot will
460    automatically overwrite the EC RW portion, using the EC RW image inside
461    the BIOS RW image.
462
463    """
464
465    def __init__(self, servo):
466        self._servo = servo
467        self._bios_programmer = FlashromProgrammer(servo, keep_ro=True)
468
469
470    def program_ec(self, image):
471        """Programs the DUT with provide ec image.
472
473        @param image: (required) location of ec image file.
474
475        """
476        # Do nothing. EC software sync will update the EC RW.
477        pass
478