• 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 = 900
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_host: a host object running commands like 'flashrom'
40      _servo_prog_state: a tuple of strings of "<control>:<value>" pairs,
41                         listing servo controls and their required values for
42                         programming
43      _servo_prog_state_delay: time in second to wait after changing servo
44                               controls for programming.
45      _servo_saved_state: a list of the same elements as _servo_prog_state,
46                          those which need to be restored after programming
47      _program_cmd: a string, the shell command to run on the servo host
48                    to actually program the firmware. Dependent on
49                    firmware/hardware type, set by subclasses.
50    """
51
52    def __init__(self, servo, req_list, servo_host=None):
53        """Base constructor.
54        @param servo: a servo object controlling the servo device
55        @param req_list: a list of strings, names of the utilities required
56                         to be in the path for the programmer to succeed
57        @param servo_host: a host object to execute commands. Default to None,
58                           using the host object from the above servo object
59        """
60        self._servo = servo
61        self._servo_prog_state = ()
62        self._servo_prog_state_delay = 0
63        self._servo_saved_state = []
64        self._program_cmd = ''
65        self._servo_host = servo_host
66        if self._servo_host is None:
67            self._servo_host = self._servo._servo_host
68
69        try:
70            self._servo_host.run('which %s' % ' '.join(req_list))
71        except error.AutoservRunError:
72            # TODO: We turn this exception into a warn since the fw programmer
73            # is not working right now, and some systems do not package the
74            # required utilities its checking for.
75            # We should reinstate this exception once the programmer is working
76            # to indicate the missing utilities earlier in the test cycle.
77            # Bug chromium:371011 filed to track this.
78            logging.warn("Ignoring exception when verify required bins : %s",
79                         ' '.join(req_list))
80
81
82    def _set_servo_state(self):
83        """Set servo for programming, while saving the current state."""
84        logging.debug("Setting servo state for programming")
85        for item in self._servo_prog_state:
86            key, value = item.split(':')
87            try:
88                present = self._servo.get(key)
89            except error.TestFail:
90                logging.warn('Missing servo control: %s', key)
91                continue
92            if present != value:
93                self._servo_saved_state.append('%s:%s' % (key, present))
94            self._servo.set(key, value)
95        time.sleep(self._servo_prog_state_delay)
96
97
98    def _restore_servo_state(self):
99        """Restore previously saved servo state."""
100        logging.debug("Restoring servo state after programming")
101        self._servo_saved_state.reverse()  # Do it in the reverse order.
102        for item in self._servo_saved_state:
103            key, value = item.split(':')
104            self._servo.set(key, value)
105
106
107    def program(self):
108        """Program the firmware as configured by a subclass."""
109        self._set_servo_state()
110        try:
111            logging.debug("Programmer command: %s", self._program_cmd)
112            self._servo_host.run(self._program_cmd,
113                                 timeout=FIRMWARE_PROGRAM_TIMEOUT_SEC)
114        finally:
115            self._restore_servo_state()
116
117
118class FlashromProgrammer(_BaseProgrammer):
119    """Class for programming AP flashrom."""
120
121    def __init__(self, servo, keep_ro=False):
122        """Configure required servo state.
123
124        @param servo: a servo object controlling the servo device
125        @param keep_ro: True to keep the RO portion unchanged
126        """
127        super(FlashromProgrammer, self).__init__(servo, ['flashrom',])
128        self._keep_ro = keep_ro
129        self._fw_path = None
130        self._tmp_path = '/tmp'
131        self._fw_main = os.path.join(self._tmp_path, 'fw_main')
132        self._wp_ro = os.path.join(self._tmp_path, 'wp_ro')
133        self._ro_vpd = os.path.join(self._tmp_path, 'ro_vpd')
134        self._rw_vpd = os.path.join(self._tmp_path, 'rw_vpd')
135        self._gbb = os.path.join(self._tmp_path, 'gbb')
136        self._servo_version = self._servo.get_servo_version()
137        self._servo_serials = self._servo._server.get_servo_serials()
138
139
140    def program(self):
141        """Program the firmware but preserve VPD and HWID."""
142        assert self._fw_path is not None
143        self._set_servo_state()
144        try:
145            wp_ro_section = [('WP_RO', self._wp_ro)]
146            rw_vpd_section = [('RW_VPD', self._rw_vpd)]
147            ro_vpd_section = [('RO_VPD', self._ro_vpd)]
148            gbb_section = [('GBB', self._gbb)]
149            if self._keep_ro:
150                # Keep the whole RO portion
151                preserved_sections = wp_ro_section + rw_vpd_section
152            else:
153                preserved_sections = ro_vpd_section + rw_vpd_section
154
155            servo_v2_programmer = 'ft2232_spi:type=servo-v2'
156            servo_v3_programmer = 'linux_spi'
157            servo_v4_with_micro_programmer = 'raiden_debug_spi'
158            servo_v4_with_ccd_programmer = 'raiden_debug_spi:target=AP'
159            if self._servo_version == 'servo_v2':
160                programmer = servo_v2_programmer
161                servo_serial = self._servo_serials.get('main')
162                if servo_serial:
163                    programmer += ',serial=%s' % servo_serial
164            elif self._servo_version == 'servo_v3':
165                programmer = servo_v3_programmer
166            elif self._servo_version == 'servo_v4_with_servo_micro':
167                # When a uServo is connected to a DUT with CCD support, the
168                # firmware programmer will always use the uServo to program.
169                servo_micro_serial = self._servo_serials.get('servo_micro')
170                programmer = servo_v4_with_micro_programmer
171                programmer += ':serial=%s' % servo_micro_serial
172            elif self._servo_version == 'servo_v4_with_ccd_cr50':
173                ccd_serial = self._servo_serials.get('ccd')
174                programmer = servo_v4_with_ccd_programmer
175                programmer += ',serial=%s' % ccd_serial
176            else:
177                raise Exception('Servo version %s is not supported.' %
178                                self._servo_version)
179            # Save needed sections from current firmware
180            for section in preserved_sections + gbb_section:
181                self._servo_host.run(' '.join([
182                    'flashrom', '-V', '-p', programmer,
183                    '-r', self._fw_main, '-i', '%s:%s' % section]),
184                    timeout=FIRMWARE_PROGRAM_TIMEOUT_SEC)
185
186            # Pack the saved VPD into new firmware
187            self._servo_host.run('cp %s %s' % (self._fw_path, self._fw_main))
188            img_size = self._servo_host.run_output(
189                    "stat -c '%%s' %s" % self._fw_main)
190            pack_cmd = ['flashrom',
191                    '-p', 'dummy:image=%s,size=%s,emulate=VARIABLE_SIZE' % (
192                        self._fw_main, img_size),
193                    '-w', self._fw_main]
194            for section in preserved_sections:
195                pack_cmd.extend(['-i', '%s:%s' % section])
196            self._servo_host.run(' '.join(pack_cmd),
197                                 timeout=FIRMWARE_PROGRAM_TIMEOUT_SEC)
198
199            # HWID is inside the RO portion. Don't preserve HWID if we keep RO.
200            if not self._keep_ro:
201                # Read original HWID. The output format is:
202                #    hardware_id: RAMBI TEST A_A 0128
203                gbb_hwid_output = self._servo_host.run_output(
204                        'gbb_utility -g --hwid %s' % self._gbb)
205                original_hwid = gbb_hwid_output.split(':', 1)[1].strip()
206
207                # Write HWID to new firmware
208                self._servo_host.run("gbb_utility -s --hwid='%s' %s" %
209                        (original_hwid, self._fw_main))
210
211            # Flash the new firmware
212            self._servo_host.run(' '.join([
213                    'flashrom', '-V', '-p', programmer,
214                    '-w', self._fw_main]), timeout=FIRMWARE_PROGRAM_TIMEOUT_SEC)
215        finally:
216            self._servo.get_power_state_controller().reset()
217            self._restore_servo_state()
218
219
220    def prepare_programmer(self, path):
221        """Prepare programmer for programming.
222
223        @param path: a string, name of the file containing the firmware image.
224        """
225        self._fw_path = path
226        # CCD takes care holding AP/EC. Don't need the following steps.
227        if self._servo_version != 'servo_v4_with_ccd_cr50':
228            faft_config = FAFTConfig(self._servo.get_board())
229            self._servo_prog_state_delay = faft_config.servo_prog_state_delay
230            self._servo_prog_state = (
231                'spi2_vref:%s' % faft_config.spi_voltage,
232                'spi2_buf_en:on',
233                'spi2_buf_on_flex_en:on',
234                'spi_hold:off',
235                'cold_reset:on',
236                'usbpd_reset:on',
237                )
238
239
240class FlashECProgrammer(_BaseProgrammer):
241    """Class for programming AP flashrom."""
242
243    def __init__(self, servo, host=None, ec_chip=None):
244        """Configure required servo state.
245
246        @param servo: a servo object controlling the servo device
247        @param host: a host object to execute commands. Default to None,
248                     using the host object from the above servo object, i.e.
249                     a servo host. A CrOS host object can be passed here
250                     such that it executes commands on the CrOS device.
251        @param ec_chip: a string of EC chip. Default to None, using the
252                        EC chip name reported by servo, the primary EC.
253                        Can pass a different chip name, for the case of
254                        the base EC.
255
256        """
257        super(FlashECProgrammer, self).__init__(servo, ['flash_ec'], host)
258        self._servo_version = self._servo.get_servo_version()
259        if ec_chip is None:
260            self._ec_chip = servo.get('ec_chip')
261        else:
262            self._ec_chip = ec_chip
263
264    def prepare_programmer(self, image):
265        """Prepare programmer for programming.
266
267        @param image: string with the location of the image file
268        """
269        # Get the port of servod. flash_ec may use it to talk to servod.
270        port = self._servo._servo_host.servo_port
271        self._program_cmd = ('flash_ec --chip=%s --image=%s --port=%d' %
272                             (self._ec_chip, image, port))
273        if self._ec_chip == 'stm32':
274            self._program_cmd += ' --bitbang_rate=57600'
275        self._program_cmd += ' --verify'
276        self._program_cmd += ' --verbose'
277
278
279class ProgrammerV2(object):
280    """Main programmer class which provides programmer for BIOS and EC with
281    servo V2."""
282
283    def __init__(self, servo):
284        self._servo = servo
285        self._valid_boards = self._get_valid_v2_boards()
286        self._bios_programmer = self._factory_bios(self._servo)
287        self._ec_programmer = self._factory_ec(self._servo)
288
289
290    @staticmethod
291    def _get_valid_v2_boards():
292        """Greps servod config files to look for valid v2 boards.
293
294        @return A list of valid board names.
295        """
296        site_packages_paths = site.getsitepackages()
297        SERVOD_CONFIG_DATA_DIR = None
298        for p in site_packages_paths:
299            servo_data_path = os.path.join(p, 'servo', 'data')
300            if os.path.exists(servo_data_path):
301                SERVOD_CONFIG_DATA_DIR = servo_data_path
302                break
303        if not SERVOD_CONFIG_DATA_DIR:
304            raise ProgrammerError(
305                    'Unable to locate data directory of Python servo module')
306        SERVOFLEX_V2_R0_P50_CONFIG = 'servoflex_v2_r0_p50.xml'
307        SERVO_CONFIG_GLOB = 'servo_*_overlay.xml'
308        SERVO_CONFIG_REGEXP = 'servo_(?P<board>.+)_overlay.xml'
309
310        def is_v2_compatible_board(board_config_path):
311            """Check if the given board config file is v2-compatible.
312
313            @param board_config_path: Path to a board config XML file.
314
315            @return True if the board is v2-compatible; False otherwise.
316            """
317            configs = []
318            def get_all_includes(config_path):
319                """Get all included XML config names in the given config file.
320
321                @param config_path: Path to a servo config file.
322                """
323                root = xml.etree.ElementTree.parse(config_path).getroot()
324                for element in root.findall('include'):
325                    include_name = element.find('name').text
326                    configs.append(include_name)
327                    get_all_includes(os.path.join(
328                            SERVOD_CONFIG_DATA_DIR, include_name))
329
330            get_all_includes(board_config_path)
331            return True if SERVOFLEX_V2_R0_P50_CONFIG in configs else False
332
333        result = []
334        board_overlays = glob.glob(
335                os.path.join(SERVOD_CONFIG_DATA_DIR, SERVO_CONFIG_GLOB))
336        for overlay_path in board_overlays:
337            if is_v2_compatible_board(overlay_path):
338                result.append(re.search(SERVO_CONFIG_REGEXP,
339                                        overlay_path).group('board'))
340        return result
341
342
343    def _get_flashrom_programmer(self, servo):
344        """Gets a proper flashrom programmer.
345
346        @param servo: A servo object.
347
348        @return A programmer for flashrom.
349        """
350        return FlashromProgrammer(servo)
351
352
353    def _factory_bios(self, servo):
354        """Instantiates and returns (bios, ec) programmers for the board.
355
356        @param servo: A servo object.
357
358        @return A programmer for ec. If the programmer is not supported
359            for the board, None will be returned.
360        """
361        _bios_prog = None
362        _board = servo.get_board()
363
364        logging.debug('Setting up BIOS programmer for board: %s', _board)
365        if _board in self._valid_boards:
366            _bios_prog = self._get_flashrom_programmer(servo)
367        else:
368            logging.warning('No BIOS programmer found for board: %s', _board)
369
370        return _bios_prog
371
372
373    def _factory_ec(self, servo):
374        """Instantiates and returns ec programmer for the board.
375
376        @param servo: A servo object.
377
378        @return A programmer for ec. If the programmer is not supported
379            for the board, None will be returned.
380        """
381        _ec_prog = None
382        _board = servo.get_board()
383
384        logging.debug('Setting up EC programmer for board: %s', _board)
385        if _board in self._valid_boards:
386            _ec_prog = FlashECProgrammer(servo)
387        else:
388            logging.warning('No EC programmer found for board: %s', _board)
389
390        return _ec_prog
391
392
393    def program_bios(self, image):
394        """Programs the DUT with provide bios image.
395
396        @param image: (required) location of bios image file.
397
398        """
399        self._bios_programmer.prepare_programmer(image)
400        self._bios_programmer.program()
401
402
403    def program_ec(self, image):
404        """Programs the DUT with provide ec image.
405
406        @param image: (required) location of ec image file.
407
408        """
409        self._ec_programmer.prepare_programmer(image)
410        self._ec_programmer.program()
411
412
413class ProgrammerV2RwOnly(ProgrammerV2):
414    """Main programmer class which provides programmer for only updating the RW
415    portion of BIOS with servo V2.
416
417    It does nothing on EC, as EC software sync on the next boot will
418    automatically overwrite the EC RW portion, using the EC RW image inside
419    the BIOS RW image.
420
421    """
422
423    def _get_flashrom_programmer(self, servo):
424        """Gets a proper flashrom programmer.
425
426        @param servo: A servo object.
427
428        @return A programmer for flashrom.
429        """
430        return FlashromProgrammer(servo, keep_ro=True)
431
432
433    def program_ec(self, image):
434        """Programs the DUT with provide ec image.
435
436        @param image: (required) location of ec image file.
437
438        """
439        # Do nothing. EC software sync will update the EC RW.
440        pass
441
442
443class ProgrammerV3(object):
444    """Main programmer class which provides programmer for BIOS and EC with
445    servo V3.
446
447    Different from programmer for servo v2, programmer for servo v3 does not
448    try to validate if the board can use servo V3 to update firmware. As long as
449    the servod process running in beagblebone with given board, the program will
450    attempt to flash bios and ec.
451
452    """
453
454    def __init__(self, servo):
455        self._servo = servo
456        self._bios_programmer = FlashromProgrammer(servo)
457        self._ec_programmer = FlashECProgrammer(servo)
458
459
460    def program_bios(self, image):
461        """Programs the DUT with provide bios image.
462
463        @param image: (required) location of bios image file.
464
465        """
466        self._bios_programmer.prepare_programmer(image)
467        self._bios_programmer.program()
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        self._ec_programmer.prepare_programmer(image)
477        self._ec_programmer.program()
478
479
480class ProgrammerV3RwOnly(ProgrammerV3):
481    """Main programmer class which provides programmer for only updating the RW
482    portion of BIOS with servo V3.
483
484    It does nothing on EC, as EC software sync on the next boot will
485    automatically overwrite the EC RW portion, using the EC RW image inside
486    the BIOS RW image.
487
488    """
489
490    def __init__(self, servo):
491        self._servo = servo
492        self._bios_programmer = FlashromProgrammer(servo, keep_ro=True)
493
494
495    def program_ec(self, image):
496        """Programs the DUT with provide ec image.
497
498        @param image: (required) location of ec image file.
499
500        """
501        # Do nothing. EC software sync will update the EC RW.
502        pass
503